[
  {
    "path": ".devcontainer/Dockerfile",
    "content": "# Update to pick an Elixir version: 1.9, 1.10, 1.10.4\nARG VARIANT=latest\nFROM elixir:${VARIANT}\n\nENV USERNAME=doge\nENV USER_UID=1000\nENV USER_GID=$USER_UID\n\nCOPY scripts/*.sh /tmp/scripts/\n\n# Create non-root doge user\nRUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\\n    && bash /tmp/scripts/user.sh \"$USERNAME\" \"$USER_UID\" \"$USER_GID\" \\\n    # Clean up\n    && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg\n\n# Install Node.js\nENV NVM_DIR=/home/$USERNAME/.nvm\nENV NODE_VERSION=\"lts/*\"\n\nRUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\\n    # Install common packages, non-root user, update yarn and install nvm\n    && bash /tmp/scripts/node.sh \"$NVM_DIR\" \"$NODE_VERSION\" \"$USERNAME\" \\\n    # Clean up\n    && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg\n\n# Cleanup scripts\nRUN rm -rf /tmp/scripts\n\nCMD [\"sleep\", \"infinity\"]"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n    \"name\": \"Dogehouse\",\n    \"dockerComposeFile\": \"docker-compose.yml\",\n    \"service\": \"workspace\",\n    \"workspaceFolder\": \"/workspace\",\n    \"extensions\": [\n        \"dbaeumer.vscode-eslint\",\n        \"esbenp.prettier-vscode\",\n        \"jakebecker.elixir-ls\",\n        \"bradlc.vscode-tailwindcss\"\n    ],\n    \"forwardPorts\": [\n        80,\n        3000,\n        4001,\n        8080,\n        5432,\n        5672,\n        15672 // rabbit.mq management interface \n    ],\n    \"postCreateCommand\": [\"bash\", \".devcontainer/scripts/environment.sh\"],\n    \"remoteUser\": \"doge\"\n}"
  },
  {
    "path": ".devcontainer/docker-compose.yml",
    "content": "version: \"3\"\n\nservices:\n  workspace:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    volumes:\n      - ..:/workspace:cached\n\n  db:\n    image: postgres:latest\n    restart: unless-stopped\n    volumes:\n      - postgres-data:/var/lib/postgresql/data\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n      POSTGRES_DB: kousa_repo2 # Initial schema name\n    network_mode: service:workspace\n\n  rabbitmq:\n    image: rabbitmq:management\n    restart: unless-stopped\n    volumes:\n      - rabbitmq-data:/var/lib/rabbitmq/data\n    environment:\n      RABBITMQ_DEFAULT_USER: guest\n      RABBITMQ_DEFAULT_PASS: guest\n    network_mode: service:workspace\n\nvolumes:\n  postgres-data:\n  rabbitmq-data:\n"
  },
  {
    "path": ".devcontainer/scripts/environment.sh",
    "content": "# kousa\ncat >> \"/home/$USERNAME/.bashrc\" << EOL\n\n# Kousa environment variables \nexport DATABASE_URL=postgres://postgres:postgres@localhost/kousa_repo2\nexport BEN_GITHUB_ID=7872329\nexport RABBITMQ_URL=amqp://guest:guest@localhost:5672\nexport ACCESS_TOKEN_SECRET=\nexport REFRESH_TOKEN_SECRET=\nexport GITHUB_CLIENT_ID=\nexport TWITTER_API_KEY=\nexport TWITTER_SECRET_KEY=\nexport TWITTER_BEARER_TOKEN=\nexport GITHUB_CLIENT_SECRET=\nexport DISCORD_CLIENT_ID=\nexport DISCORD_CLIENT_SECRET=\nexport GOOGLE_CLIENT_ID=\nexport GOOGLE_CLIENT_SECRET=\nexport SENTRY_DNS=\nexport API_URL=http://localhost:4001\nexport WEB_URL=http://localhost:3000\nexport PORT=4001\nEOL\n\n# shawarma\necho \"WEBRTC_LISTEN_IP=127.0.0.1\" > shawarma/.env\n\n# kibbeh\ncp kibbeh/.env.example kibbeh/.env\n"
  },
  {
    "path": ".devcontainer/scripts/node.sh",
    "content": "#!/usr/bin/env bash\n\nUSERNAME=${3:-\"doge\"}\nexport NVM_DIR=${1:-\"/home/$USERNAME/.nvm\"}\nexport NODE_VERSION=${2:-\"lts/*\"}\n\nset -e\n\n# install all dependencies\napt-get update \\\n    && apt-get install -y curl ca-certificates tar gnupg2 \\\n    && apt-get -y autoclean\n\n# install yarn\ncurl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT)\necho \"deb https://dl.yarnpkg.com/debian/ stable main\" | tee /etc/apt/sources.list.d/yarn.list\napt-get update\napt-get -y install --no-install-recommends yarn\n\n# install nvm\nsu ${USERNAME} -c \"mkdir $NVM_DIR\"\nsu ${USERNAME} -c \"curl --silent -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash\"\n\n# install node and npm\nsu ${USERNAME} -c \"source $NVM_DIR/nvm.sh \\\n    && nvm install $NODE_VERSION \\\n    && nvm alias default $NODE_VERSION \\\n    && nvm use default\""
  },
  {
    "path": ".devcontainer/scripts/user.sh",
    "content": "#!/usr/bin/env bash\n\nUSERNAME=${1:-\"doge\"}\nUSER_UID=${2:-1000}\nUSER_GID=${3:-$USER_UID}\n\nPACKAGE_LIST=\"apt-utils \\\n        git \\\n        htop \\\n        curl \\\n        wget \\\n        unzip \\\n        zip \\\n        vim \\\n        less \\\n        sudo \\\n        man-db\"\n\n# install packages\napt-get -y install --no-install-recommends ${PACKAGE_LIST}\n\n# add non-root user\ngroupadd --gid $USER_GID $USERNAME\nuseradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME\necho $USERNAME ALL=\\(root\\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\nchmod 0440 /etc/sudoers.d/$USERNAME\nchown ${USERNAME}:${USERNAME} \"/home/${USERNAME}/.bashrc\""
  },
  {
    "path": ".dockerignore",
    "content": "docker/\n.docker/"
  },
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules/"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**What device are you on?**\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "kibbeh:\n  - kibbeh/**/*\nbaklava:\n  - baklava/**/*\nkebab:\n  - kebab/**/*\nkousa:\n  - kousa/**/*\npilaf:\n  - pilaf/**/*\nshawarma:\n  - shawarma/**/*\ndolma:\n  - dolma/**/*\nemote:\n  - kibbeh/public/emotes/**/*\ntranslation:\n  - kibbeh/public/locales/**/*\n"
  },
  {
    "path": ".github/workflows/baklava-build_and_release.yaml",
    "content": "name: baklava:build_and_release\n\non:\n  push:\n    paths:\n      - \"baklava/**\"\n    branches:\n      - staging\n\njobs:\n  release:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n    steps:\n      - name: Check out Git repository\n        uses: actions/checkout@v1\n      - name: Install Node.js, NPM and Yarn\n        uses: actions/setup-node@v1\n        with:\n          node-version: 14\n      - name: Yarn cache\n        uses: actions/cache@v2\n        id: yarn-cache\n        with:\n          path: |\n            baklava/.yarn/cache\n            baklava/resources/overlay/.yarn/cache\n          key: ${{ runner.os }}-yarn-${{ hashFiles('baklava/**/yarn.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-yarn-\n      - name: Install deps\n        working-directory: baklava\n        run: yarn\n      - name: Save env vars\n        working-directory: baklava/src\n        run: echo \"export const DISCORD_CLIENT_ID = '${{ secrets.DISCORD_CLIENT_ID }}'\" >> constants.ts\n      - name: Compile Typescript\n        working-directory: baklava\n        run: yarn compile\n      - name: Install Overlay Deps\n        working-directory: baklava/resources/overlay\n        run: yarn\n      - name: Build overlay\n        working-directory: baklava/resources/overlay\n        run: yarn build\n      - name: Install rust\n        uses: hecrj/setup-rust-action@v1\n        with:\n          rust-version: stable\n      - name: Install nj-cli\n        run: cargo install nj-cli\n      - name: Build globalkey\n        working-directory: baklava\n        run: yarn build:globalkey\n      - name: Prepare for app notarization\n        if: startsWith(matrix.os, 'macos')\n        # Import Apple API key for app notarization on macOS\n        # this get skipped for some reason\n        run: |\n          mkdir -p ~/private_keys/\n          echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8\n      - name: Install Snapcraft\n        uses: samuelmeuli/action-snapcraft@v1\n        if: startsWith(matrix.os, 'ubuntu')\n        with:\n          # Log in to Snap Store\n          snapcraft_token: ${{ secrets.snapcraft_token }}\n      - name: Install RPM\n        if: startsWith(matrix.os, 'ubuntu')\n        run: |\n          sudo apt install rpm\n      - name: Build/release Electron app\n        uses: samuelmeuli/action-electron-builder@v1\n        with:\n          mac_certs: ${{ secrets.mac_certs }}\n          mac_certs_password: ${{ secrets.mac_certs_password }}\n          # this is executed before the default `electron-builder` script\n          build_script_name: delete:artifacts\n          package_root: baklava\n          github_token: ${{ secrets.github_token }}\n          release: true\n        env:\n          # macOS notarization API key\n          API_KEY_ID: ${{ secrets.api_key_id }}\n          API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}\n"
  },
  {
    "path": ".github/workflows/dolma-npm_deploy.yaml",
    "content": "name: dolma:npm_publish\non:\n  push:\n    paths:\n      - \"dolma/**\"\n    branches:\n      - staging\njobs:\n  npm-publish:\n    name: npm-publish\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n      - name: Install Node.js, NPM and Yarn\n        uses: actions/setup-node@v1\n        with:\n          node-version: 14\n      - name: Yarn cache\n        uses: actions/cache@v2\n        id: yarn-cache\n        with:\n          path: |\n            .yarn/cache\n          key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-yarn-\n      - name: Install deps\n        working-directory: ./dolma\n        run: yarn\n      - name: Build\n        working-directory: ./dolma\n        run: yarn build\n      - name: Publish if version has been updated\n        uses: pascalgn/npm-publish-action@1.3.7\n        with: # All of theses inputs are optional\n          tag_name: \"v%s\"\n          tag_message: \"v%s\"\n          create_tag: \"false\"\n          commit_pattern: \"^feat\\\\(dolma\\\\)\\\\: release (\\\\S+)\"\n          workspace: \"dolma\"\n          publish_command: \"yarn\"\n          publish_args: \"--non-interactive --verbose\"\n        env: # More info about the environment variables in the README\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated\n          NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings\n"
  },
  {
    "path": ".github/workflows/global-label_pr.yml",
    "content": "name: global:label_pr\n\non:\n  - pull_request_target\n\njobs:\n  triage:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/labeler@main\n        with:\n          repo-token: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/global-lint_commit_messages.yaml",
    "content": "name: global:lint_commit_messages\n\non: [push, pull_request]\n\njobs:\n  commitlint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: wagoid/commitlint-github-action@v3"
  },
  {
    "path": ".github/workflows/globalkey-npm_deploy.yaml",
    "content": "name: globalkey:npm_publish\non:\n  push:\n    paths:\n      - \"globalkey/**\"\n    branches:\n      - staging\njobs:\n  npm-publish:\n    name: npm-publish\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n      - name: Install Node.js, NPM and Yarn\n        uses: actions/setup-node@v1\n        with:\n          node-version: 14\n      - name: Install deps\n        working-directory: ./globalkey\n        run: yarn\n      - name: Build\n        working-directory: ./globalkey\n        run: yarn build\n      - name: Publish if version has been updated\n        uses: pascalgn/npm-publish-action@1.3.7\n        with: # All of theses inputs are optional\n          tag_name: \"v%s\"\n          tag_message: \"v%s\"\n          create_tag: \"false\"\n          commit_pattern: \"^feat\\\\(globalkey\\\\)\\\\: release (\\\\S+)\"\n          workspace: \"globalkey\"\n          publish_command: \"yarn\"\n          publish_args: \"--non-interactive --verbose\"\n        env: # More info about the environment variables in the README\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated\n          NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings\n"
  },
  {
    "path": ".github/workflows/kebab-npm_publish.yaml",
    "content": "name: kebab:npm_publish\non:\n  push:\n    paths:\n      - \"kebab/**\"\n    branches:\n      - staging\njobs:\n  npm-publish:\n    name: npm-publish\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n      - name: Install Node.js, NPM and Yarn\n        uses: actions/setup-node@v1\n        with:\n          node-version: 14\n      - name: Yarn cache\n        uses: actions/cache@v2\n        id: yarn-cache\n        with:\n          path: |\n            .yarn/cache\n          key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-yarn-\n      - name: Install deps\n        working-directory: kebab\n        run: yarn\n      - name: Build\n        working-directory: kebab\n        run: yarn build\n      - name: Publish if version has been updated\n        uses: pascalgn/npm-publish-action@1.3.7\n        with: # All of theses inputs are optional\n          tag_name: \"v%s\"\n          tag_message: \"v%s\"\n          create_tag: \"false\"\n          commit_pattern: \"^feat\\\\(kebab\\\\)\\\\: release (\\\\S+)\"\n          workspace: \"kebab\"\n          publish_command: \"yarn\"\n          publish_args: \"--non-interactive\"\n        env: # More info about the environment variables in the README\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated\n          NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings\n"
  },
  {
    "path": ".github/workflows/kibbeh-e2e_tests.yaml",
    "content": "name: kibbeh:e2e_tests\non:\n  push:\n    branches:\n      - staging\n    paths:\n      - '.github/workflows/kibbeh-e2e_tests.yaml'\n      - 'kousa/lib/**'\n      - 'kibbeh/src/**'\n  pull_request:\n    branches:\n      - staging\n    paths:\n      - 'kousa/lib/**'\n      - 'kibbeh/src/**'\njobs:\n  end-to-end-tests:\n    name: end-to-end-tests\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: ./kousa\n    services:\n      db:\n        image: postgres:11\n        ports: ['5432:5432']\n        env:\n          POSTGRES_PASSWORD: postgres\n          POSTGRES_DB: kousa_repo2\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n    steps:\n    - uses: actions/checkout@v2\n    - uses: erlef/setup-elixir@v1\n      with:\n        otp-version: '22.2'\n        elixir-version: '1.11'\n    - name: fetch deps\n      run: mix deps.get\n      env:\n          MIX_ENV: test\n    - run: source .envrc && mix ecto.migrate\n    - run: source .envrc && iex -S mix\n    - name: Use Node.js\n      uses: actions/setup-node@v1\n    - run: yarn install\n    - run: cd ../kebab && yarn build\n    - run: cd ../kibbeh && yarn test:e2e:ci"
  },
  {
    "path": ".github/workflows/kibbeh-lint_and_test.yaml",
    "content": "name: kibbeh:lint_and_test\n\non:\n  push:\n    branches:\n      - staging\n    paths:\n      - \"kibbeh/**\"\n  pull_request:\n    branches:\n      - staging\n    paths:\n      - \"kibbeh/**\"\n\njobs:\n  test:\n    name: lint-and-test-kibbeh\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Use Node.js\n        uses: actions/setup-node@v1\n      - name: Yarn cache\n        uses: actions/cache@v2\n        id: yarn-cache\n        with:\n          path: |\n            .yarn/cache\n          key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-yarn-\n      - run: yarn install\n      - run: cd kebab && yarn build\n      - run: cd dolma && yarn build\n      - run: cd kibbeh && yarn compile\n      - run: cd kibbeh && yarn lint\n      - run: cd kibbeh && yarn test:ci\n        id: test\n"
  },
  {
    "path": ".github/workflows/kousa-deploy_staging.yaml",
    "content": "name: kousa:deploy_staging\non:\n  push:\n    paths:\n      - 'kousa/lib/**'\n    branches:\n      - staging\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: executing remote ssh commands using password\n        uses: appleboy/ssh-action@master\n        with:\n          host: ${{ secrets.SSH_HOST }}\n          username: ${{ secrets.SSH_USERNAME }}\n          password: ${{ secrets.SSH_PASSWORD }}\n          script: cd dogehouse/kousa && git pull origin staging && docker build -t benawad/kousa:0.0.1 . && docker tag benawad/kousa:0.0.1 dokku/doge-staging:latest && dokku tags:deploy doge-staging latest\n"
  },
  {
    "path": ".github/workflows/kousa-tests.yaml",
    "content": "name: kousa:tests\non:\n  push:\n    branches:\n      - staging\n    paths:\n      - 'kousa/lib/**'\n  pull_request:\n    branches:\n      - staging\n    paths:\n      - 'kousa/lib/**'\njobs:\n  test:\n    name: test-elixir\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: ./kousa\n    services:\n      db:\n        image: postgres:11\n        ports: ['5432:5432']\n        env:\n          POSTGRES_PASSWORD: postgres\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n    steps:\n    - uses: actions/checkout@v2\n    - uses: erlef/setup-elixir@v1\n      with:\n        otp-version: '22.2'\n        elixir-version: '1.11'\n    - name: fetch deps\n      run: mix deps.get\n      env:\n          MIX_ENV: test\n    - run: mix test\n      id: test"
  },
  {
    "path": ".github/workflows/shawarma-deploy_staging.yaml",
    "content": "name: shawarma:deploy_staging\non:\n  push:\n    paths:\n      - 'shawarma/src/**'\n    branches:\n      - staging\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: executing remote ssh commands using password\n        uses: appleboy/ssh-action@master\n        with:\n          host: ${{ secrets.SSH_HOST }}\n          username: ${{ secrets.SSH_USERNAME }}\n          password: ${{ secrets.SSH_PASSWORD }}\n          script: cd dogehouse/kousa && git pull origin staging && source ~/.nvm/nvm.sh && nvm use 14 && cd ../shawarma && yarn install && npm run build && pm2 restart dist/index.js\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n\n# Local Netlify folder\n.netlify\n\n.idea/\n*/.idea/\n.docker\n.env\n.next\nstorybook-static\n_\n.env.local\nkebab/lib\nyarn-error.log\n\ndist\n.yarn\n!.yarn/releases\n!.yarn/plugins\n!.yarn/sdks\n!.yarn/versions\n.pnp.*\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n# @todo uncomment when new-design hits\n# thats when we will start enforcing commit style\n# npx --no-install commitlint --edit\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n# . \"$(dirname \"$0\")/_/husky.sh\"\n\n# cd kibbeh\n# npm run format\n# npm run lint"
  },
  {
    "path": ".prettierignore",
    "content": "node_modules/"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  trailingComma: \"es5\",\n  tabWidth: 2,\n  semi: true,\n  singleQuote: false,\n  arrowParens: \"always\",\n  useTabs: false,\n};\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"elixirLS.projectDir\": \"kousa\",\n    \"elixir.projectPath\": \"kousa\",\n    \"git.ignoreLimitWarning\": true,\n}"
  },
  {
    "path": ".yarnrc.yml",
    "content": "nodeLinker: node-modules\n\nplugins:\n  - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs\n    spec: \"@yarnpkg/plugin-workspace-tools\"\n  - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs\n    spec: \"@yarnpkg/plugin-interactive-tools\"\n\nyarnPath: .yarn/releases/yarn-berry.cjs\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 4/16/2021\n\n- Switched to a new UI\n\n## 2/20/2021\n\n- Added changelog\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "/kebab/ @overlisted\n/baklava/ @amitojsingh366\n/dolma/ @HoloPanio\n/globalkey/ @willdoescode\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to make participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at benawadapps@gmail.com  or on <a href=\"https://discord.gg/wCbKBZF9cV\">Discord</a> . All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to DogeHouse\n> Please read the [PRIORITY LIST](https://github.com/benawad/dogehouse/issues/1969) before contributing.\n\nWe love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:\n- Reporting an issue\n- Discussing the current state of the code\n- Submitting a fix\n- Proposing new features\n- Becoming a maintainer\n\n## Code of Conduct\nThe code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).\n\n## Our Development Process\nAll changes happen through pull requests. Pull requests are the best way to propose changes. We actively welcome your pull requests and invite you to submit pull requests directly [here](https://github.com/benawad/dogehouse/pulls), and after review, these can be merged into the project.\n\n## Using the Project's Standard Commit Messages\nThis project is using the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0-beta.2/) standard. Please follow these steps to ensure your\ncommit messages are standardized:\n1. Make sure your shell path is in the root of the project (not inside any of the packages).\n2. Run `yarn`.\n3. Stage the files you are committing with `git add [files]`.\n4. Run `yarn commit`. This will start an interactive prompt that generates your commit message:\n    1. Select the type of change.\n    2. Type the scope. This is either `global` for project-wide changes or one of the packages (kibbeh, shawarma etc.).\n    3. Write a short, imperative tense description of the change.\n    4. If the above was not sufficient, you may now write a longer description of your change (otherwise press enter to leave blank).\n    5. y or n for whether there are any breaking changes (e.g. changing the props of a component, changing the JSON structure of an API response).\n    6. y or n for whether this change affects an open issue, if positive you will be prompted to enter the issue number.\n5. Your commit message has now been created, you may push to your fork and open a pull request (read below for further instructions).\n\n## Pull Requests\n1. Fork the repo and create your branch (usually named `patch-%the number of PRs you've already made%`) from `staging`.\n2. If you've added code that should be tested, add some test examples.\n3. Ensure to describe your pull request.\n\n## Adding Emojis\nEmojis need to be 28x28px. To add an emoji, add the png/gif image to public/emojis and add the emoji to the `kofta/src/app/modules/room-chat/EmoteData.ts`.\n\nTo avoid conflicts please add the emojis to the top of the file.\n> **NOTE:** We are not accepting new emojis atm\n\n\n## Quickstart Local Frontend Development\nDo this if you only want to do React stuff and don't want to touch Elixir:\n\n### UI *(react + next.js)*:\nNavigate to `/kibbeh`\n\n- Run `yarn`\n- Run `yarn staging` (this tells React to connect to a hosted version of the backend for development purposes).\n- Read `kibbeh/README.md` for more information and fixes for known development issues.\n> **NOTE:** Please follow the [design guidelines](https://github.com/benawad/dogehouse/blob/staging/DESIGN_GUIDELINES.md) and [figma mockups](https://www.figma.com/file/CS01VVLR7ArQl0afYFkNj3/Web-App) and if what you're trying to do isn't in there, consult [@ajmnz](https://github.com/ajmnz)/[@benawad](https://github.com/benawad) beforehand.\n\n## Translating\n1. Fork the [repository](https://github.com/benawad/dogehouse \"benawad/dogehouse\") (click on `fork` in the top right corner of the screen)\n![image](https://i.ibb.co/RB4FVS0/Screenshot-2021-05-07-152827.jpg)\n\n2. In the forked repository, navigate to `kibbeh/public/locales` and then choose your language and open the `translation.json` file\n3. Click on `edit` in the top right corner of the window\n\n![image](https://i.ibb.co/vZjt4jD/Screenshot-2021-05-07-153427.jpg)\n\n4. Make the changes in the translation(make sure you are using the correct json syntax)\n5. click `commit changes` in the bottom of the page and add `fix(kibbeh): update {my language} Translation` as the commit message(`fix` if you are fixing tranlsations and `feat` if you are adding a language)(leave the description empty!)\n![image](https://user-images.githubusercontent.com/68110106/117442435-6e1b1080-af3f-11eb-990f-9a1a270fef29.png)\n6. Go to the [main page of the repository](https://github.com/benawad/dogehouse) and under `Contribute`, click on `Open Pull Request`\n\n## Supporting translation in new components\n1. Add your translation key into the English `translation.json` located in `kibbeh/public/locales/en/translation.json`. Make sure it is put in an appropriate section that makes sense.\n2. Run `yarn i18`. This will sync all fields in all languages, if field doesn't exist it will copy it over from `en`.\n3. Use your translation key in your code. This is done by using `useTypeSafeTranslation` like this: `const { t } = useTypeSafeTranslation();`. You can now call `t` and get your desired translation key.\n\n## Devcontainer Full Local Development\nFor VSCode users, we're able to use devcontainers which allows you to create development environments that already have all the tools and services configured and ready to go.\n\n### Usage\n\n_Prerequisite: [Install Docker](https://docs.docker.com/install) on your local environment._\n\nTo get started, read and follow the instructions in [Developing inside a Container](https://code.visualstudio.com/docs/remote/containers). The [.devcontainer/](./.devcontainer) directory contains pre-configured `devcontainer.json`, `docker-compose.yml` and `Dockerfile` files, which you can use to set up remote development within a docker container.\n\n- Install the [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension.\n- Open VSCode and bring up the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).\n- Type `Remote-Containers: Open Folder in Container`, this will build the container with Elixir and Node installed, this will also start Postgres and RabbitMQ instances.\n\n> If you need to modify environment variables for kousa, you need to modify them inside `/home/doge/.bashrc` and restart your terminal.\n\n### Run\n#### `kousa`\n```shell\n$ mix deps.get\n$ mix ecto.migrate\n$ iex -S mix\n```\n#### `shawarma`\n```shell\n$ yarn\n$ yarn build\n$ yarn start\n```\n#### `kibbeh`\n```shell\n$ yarn\n$ yarn dev\n```\n\n## Manual Full Local Development\nHow to run locally:\n\n### Backend\n#### RabbitMQ\nInstall RabbitMQ:\n- **macOS**: Run `brew install rabbitmq`.\n- **Windows**: Run `choco install rabbitmq`.\n- **Linux**: Follow their installation guide [here](https://www.rabbitmq.com/download.html).\n\nStart RabbitMQ\n- **macOS**: Run `brew services start rabbitmq`.\n- **Windows**: Setup guide [here](https://www.rabbitmq.com/install-windows.html).\n- **Linux**: Setup guide [here](https://www.rabbitmq.com/install-debian.html).\n\n#### PostgreSQL\nInstall PostgreSQL:\n- **macOS**: Run `brew install postgresql`.\n- **Windows**: Follow [this](https://www.postgresqltutorial.com/install-postgresql/) guide.\n- **Linux**: Follow [this](https://www.postgresqltutorial.com/install-postgresql-linux/) guide.\n\nStart PostgreSQL:\n- **macOS**: Run `brew services start postgresql`.\n- **Windows**: Start PostgreSQL through the control panel or run `net start postgresql-{version}`.\n- **Linux**: Run `/etc/rc.d/init.d/postgresql start`.\n\nCreate a DB named `kousa_repo2`:\n\n```shell\n$ psql postgres\n\n$ CREATE DATABASE kousa_repo2;\n```\n\n#### Elixir\nElixir installation guide [here](https://elixir-lang.org/install.html).\n\n#### `kousa`\nNavigate to `/kousa` and set the following environment variables:\n```\nexport DATABASE_URL=postgres://user:password@localhost/kousa_repo2\nexport BEN_GITHUB_ID=7872329\nexport RABBITMQ_URL=amqp://user:password@yourinternalip:5672\nexport ACCESS_TOKEN_SECRET=\nexport REFRESH_TOKEN_SECRET=\nexport GITHUB_CLIENT_ID=\nexport TWITTER_API_KEY=\nexport TWITTER_SECRET_KEY=\nexport TWITTER_BEARER_TOKEN=\nexport GITHUB_CLIENT_SECRET=\nexport SENTRY_DNS=\nexport API_URL=http://localhost:4001\nexport WEB_URL=http://localhost:3000\nexport PORT=4001\nexport DISCORD_CLIENT_ID=\nexport DISCORD_CLIENT_SECRET=\n```\n\n> You can save these variables in a `.txt` and run `source path/to/file.txt`\n\nRun the following commands:\n```shell\n$ mix deps.get\n$ mix ecto.migrate\n```\n\nStart the server\n```shell\n$ iex -S mix\n```\n\n#### `shawarma`\nNavigate to `/shawarma` and run `yarn`.\n\n> Mediasoup requires `node >=0.8 <=14` and has [specific requirements](https://mediasoup.org/documentation/v3/mediasoup/installation/#windows) on Windows.\n\nCreate an `.env` file and set the following environment variable:\n\n```\nWEBRTC_LISTEN_IP=127.0.0.1\n```\n\nThen run `yarn build` and `yarn start`.\n\n## Issues\n> NOTE: If your bug is a **security vulnerability**, please instead see the [security policy](https://github.com/benawad/dogehouse/security/policy)\n\nWe use GitHub issues to track public bugs. Please ensure your description is\nclear and has sufficient instructions to be able to reproduce the issue. Report a bug by <a href=\"https://github.com/benawad/dogehouse/issues\">opening a new issue</a>; it's that easy!\n\n## Frequently Asked Questions (FAQs)\n<!--- I thought it would be great to have a list of FAQs for the project to help save time for new contributors--->\n    - Q: [The Question?]\n    - A: [The Answer!]\n\n## Feature Request\nGreat Feature Requests tend to have:\n\n- A quick idea summary.\n- What & why you wanted to add the specific feature.\n- Additional context like images, links to resources to implement the feature etc, etc.\n\n## License\nBy contributing to DogeHouse, you agree that your contributions will be licensed\nunder the [LICENSE file](LICENSE).\n"
  },
  {
    "path": "CREATE_BOT_ACCOUNT.MD",
    "content": "0. Sign in to your account\n1. Head to [developer settings](https://dogehouse.tv/developer/bots)\n2. Click `Create bot`\n3. Enter the name of your bot\n4. Click create bot\n5. Go to the bot and click `Copy` to copy your API key\n6. You now have a bot account! Use the API key to authenticate your bot\n"
  },
  {
    "path": "DESIGN_GUIDELINES.md",
    "content": "# Design Guidelines\n\nThis document aims to specify and define the rules and patterns to follow when implementing and developing new features and components for DogeHouse.\n\n>This is a summary. For a more extensive version, components and UI Design, visit the publically available **[Figma file](https://www.figma.com/file/CS01VVLR7ArQl0afYFkNj3/Web-App?node-id=201%3A1979)**.\n\n## Table of contents\n- [Design Guidelines](#design-guidelines)\n  - [Table of contents](#table-of-contents)\n  - [Color scheme](#color-scheme)\n      - [Gray shades](#gray-shades)\n  - [Typography](#typography)\n      - [Web Embed](#web-embed)\n    - [Desktop](#desktop)\n  - [Spacing](#spacing)\n  - [Doubts and questions](#doubts-and-questions)\n\n## Color scheme\n\n- **Accent**: `#FD4D4D`\n- **Pure White**: `#FFFFFF`\n\n#### Gray shades\n- **Gray 100**: `#DEE3EA`\n- **Gray 200**: `#B2BDCD`\n- **Gray 300**: `#5D7290`\n- **~~Gray 400~~**: `#4F617A`\n- **~~Gray 500~~**: `#404F64`\n- **~~Gray 600~~**: `#323D4D`\n- **Gray 700**: `#242C37`\n- **Gray 800**: `#151A21`\n- **Gray 900**: `#0B0E11`\n\n## Typography\n\nThe font chosen for this project is `Inter`.\n\nInter is a free font available on Google Fonts. Clean and bold headings, readable paragraph text and an overall versatile font.\n\nWe'll be using two of its styles:\n- Inter Bold (`700`)\n- Inter Medium (`500`)\n\n#### Web Embed\n\nHTML's `link` method\n\n```html\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=swap\" rel=\"stylesheet\">\n```\n\nCSS/SCSS `@import`\n\n```css\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=swap');\n```\n\nCSS Rules\n\n```scss\nfont-family: 'Inter', sans-serif;\n\n// Regular\nfont-weight: 500;\n\n// Bold\nfont-weight: 700;\n```\n\n![DogeHouse typography](https://i.imgur.com/A1pz7UD.png)\n\nTag | Font Size | Line Height | Weight\n--- | --------- | ----------- | ------\n**H1** | 56px | 90 | 700\n**H2** | 40px | 64 | 700\n**H3** | 28px | 45 | 700\n**H4** | 20px | 32 | 700\n**P** | 14px | 22 | 500 - 700\n**P (small)** | 12px | 22 | 500 - 700\n\n## Spacing\n\nThis is an approximation. On some circumstances other values will be used to ensure readability, consistency and visual balance, so make sure to also check the UI Design and the spacing used there.\n\n![DogeHouse spacing](https://i.imgur.com/gRIJAXA.png)\n\n## Doubts and questions\nIf you have any doubts or concerns when developing components or other UI elements, you can open an issue and tag @ajmnz or leave a message in `#design` or `#kibbeh` in [our Discord](https://discord.gg/82HzQCJCDg).\n"
  },
  {
    "path": "FAQ.md",
    "content": "# FAQ\n\n## Why did you choose Elixir?\n\nIt's fun to program in. The error messages are awful and there's no static typing, but other than that it's great.\n\n## How will DogeHouse make money?\n\n- DogeNitro\n- DogeSubscriptions\n- DogeAds\n\n## Will open source contributors get paid?\n\nAfter DogeHouse adds monetization and it makes enough to pay server costs, there will be bounties.\n\n## How will you market this?\n\nOnce the core product is solid, I will be doing events with the community and other influencers.\n\n## Are you working on this full-time?\n\nyes\n\n## Discord Stages?\n\nhttps://www.youtube.com/watch?v=FmAL5qvJkaI\n\n## Are you ever doing video?\n\nNot for a long time, maybe never.\n"
  },
  {
    "path": "HOW_TO_DEBUG_AUDIO.md",
    "content": "0. Turn on audio debugging mode by clicking on your avatar in the top right and click `Debug Audio`:\n![image](https://user-images.githubusercontent.com/7872329/116554048-5d73f600-a8c0-11eb-9f0c-bfae58530f75.png)\n1. In a room make sure your transport is connected and your browser is supported (a warning will show up if it isn't)\n![image](https://user-images.githubusercontent.com/7872329/116554680-1803f880-a8c1-11eb-9535-aa89d2f424ca.png)\n2. Avatars will be tinted red if the audio consumer doesn't exist or orange if it's closed (this means something is wrong if other people can hear them, skip to step 6)\n3. Green will come from the bottom of the avatar if everything is working based on how loud they are speaking\n![image](https://user-images.githubusercontent.com/7872329/116556759-6e723680-a8c3-11eb-913c-7506d875ac30.png)\n\n4. If you see green but can't hear them, make sure your speakers work then click on the profile image of a speaker you can't hear and screen shot the debug info\n![image](https://user-images.githubusercontent.com/7872329/116554578-f86cd000-a8c0-11eb-883c-3a66a94eb61e.png)\n5. Then click `Force Play Audio` once\n6. If all of these things don't work, right-click the page, click inspect, click console tab, and copy/paste all the text into a GitHub issue\n"
  },
  {
    "path": "HOW_TO_GET_PERMA_BANNED_FROM_DOGEHOUSE.md",
    "content": "# Things that will get you perma banned\n\n0. Spam creating a ton of scheduled rooms\n1. NSFW profile/banner image\n2. Ban evading rooms\n - if you get banned from a room, don't create another DogeHouse account and join the same room\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (C) 2021 Ben Awad and the DogeHouse contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<a href=\"https://dogehouse.tv\"><p align=\"center\">\n<img height=100 src=\"https://raw.githubusercontent.com/benawad/dogehouse/staging/.redesign-assets/dogehouse_logo.svg\"/>\n\n</p></a>\n<p align=\"center\">\n  <strong>Taking voice conversations to the moon 🚀</strong>\n</p>\n<p align=\"center\">\n  <a href=\"https://discord.gg/wCbKBZF9cV\">\n    <img src=\"https://img.shields.io/discord/810571477316403233?style=for-the-badge\" alt=\"discord - users online\" />\n  </a>\n  <a href=\"https://dogehouse.tv\">\n    <img src=\"https://img.shields.io/endpoint?color=FD4D4D&style=for-the-badge&url=https%3A%2F%2Fapi.dogegarden.net%2Fv1%2Fshields\" alt=\"dogehouse - users online\" />\n  </a>\n</p>\n\n<h3 align=\"center\">\n  <a href=\"https://github.com/benawad/dogehouse/blob/staging/CONTRIBUTING.md\">Contribute</a>\n  <span> · </span>\n  <a href=\"https://discord.gg/82HzQCJCDg\">Community</a>\n  <span> · </span>\n  <a href=\"https://github.com/FotieMConstant/dogehouse-docs\">Documentation</a>\n</h3>\n\n---\n\n## Important DogeHouse Notice\n\n[I'm done with DogeHouse](https://www.youtube.com/watch?v=I8PkQgPiSq8)\n\n## Structure\n\n| Codebase              |      Description          |\n| :-------------------- | :-----------------------: |\n| [kousa](kousa)        |      Elixir API           |\n| [shawarma](shawarma)  |     Voice Server          |\n| [dinner](dinner)      | Puppeteer shenanigans     |\n| [baklava](baklava)    |   Electron Wrapper        |\n| [pilaf](pilaf)        |   React Native App        |\n| [kibbeh](kibbeh)      |   Next.js frontend        |\n| [kebab](kebab)        |      API Client           |\n| [dolma](dolma)        | Chat Token Transcoder     |\n| [globalkey](globalkey)| [Baklava](baklava)'s Global Keystroke Listener |\n\n\n## Branches\n\n- staging -> pr this branch for everything\n- prod -> don't touch, this is what's running in prod\n\n## Contributions\n\nDogeHouse is open to contributions, but I recommend creating an issue or replying in a comment to let me know what you are working on first that way we don't overwrite each other.\n\nPlease read [CONTRIBUTING.md](https://github.com/benawad/dogehouse/blob/staging/CONTRIBUTING.md) for details on this project.\n\n## DogeHouse Desktop\n\nA desktop app built with [Electron](https://www.electronjs.org/) is available for Windows, Mac, and Linux.\n\nThere are different ways to get the Electron desktop app:\n\n* Get the official builds from [here, in GitHub Releases][gh-releases]\nfor any platform.\n* Get it from AUR (unofficial package) for Arch/Manjaro or other Arch-based distro with\n`yay -S dogehouse`, using another AUR helper, or installing manually from the AUR.\n* Get the desktop client for Debian-based distros (including Ubuntu)\nfrom the official APT repo with these simple steps:\n  * Add the repo with `echo \"deb http://ppa.dogehouse.tv/ ./\" | sudo tee -a /etc/apt/sources.list > /dev/null`\n  * Add Ben Awad's GPG key with `$(command -v curl>>/dev/null && echo \"curl -o-\" || echo \"wget -q0-\") http://ppa.dogehouse.tv/KEY.gpg | sudo apt-key add -`.\n  * Finally, update your local repository list and install DogeHouse\nwith `sudo apt update && sudo apt install dogehouse`.\n* Get the snap for your systemd-powered Linux distro from either the\n[Snap Store](https://snapcraft.io/dogehouse) or in an terminal with\n`sudo snap install dogehouse`.\n  * After installing the snap, you need to allow microphone access with\n`sudo snap connect dogehouse:audio-record` to be able to speak in rooms.\n\n[gh-releases]: https://github.com/benawad/dogehouse/releases/latest\n\n**_Notes:_**\n\n- If a warning message pops up on Windows, go to 'more info' and select 'Run Anyway'\n- Currently, the snap package's available channels are only `edge` as\ncontributions for Baklava are merged almost on daily basis. Tested\nversions that are stable will be promoted into `stable` in the future.\n\n## DogeReviewers\n\nContributors helping to review/merge pull requests:\n\n- [@HarrisonMayotte](https://github.com/HarrisonMayotte)\n- [@TheOtterlord](https://github.com/TheOtterlord)\n- [@amitojsingh366](https://github.com/amitojsingh366)\n- [@dk-raw](https://github.com/dk-raw)\n- [@ermalsh](https://github.com/ermalsh)\n- [@goldyydev](https://github.com/goldyydev)\n- [@jamesql](https://github.com/jamesql)\n- [@nadirabbas](https://github.com/nadirabbas)\n- [@ofsho](https://github.com/ofsho)\n- [@overlisted](https://github.com/overlisted)\n\n## Code of Conduct\n\nPlease read [CODE_OF_CONDUCT.md](https://github.com/benawad/dogehouse/blob/staging/CODE_OF_CONDUCT.md) for details on our code of conduct.\n\n## How to run locally\n\nCheck <a href=\"https://github.com/benawad/dogehouse/blob/staging/CONTRIBUTING.md#quickstart-local-frontend-development\">here</a> on how to run locally</a>\n\n## Why did you make this?\n\nhttps://www.youtube.com/watch?v=hy-EhJ_tTQo\n\n## Attribution\n\nFor emojis, we use [Twemoji](https://twemoji.twitter.com/)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nDM me on https://twitter.com/benawad or https://discord.gg/wCbKBZF9cV\n"
  },
  {
    "path": "baklava/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/builds\n/dist\n\n#Builds\nDogeHouse*\n\n# misc\n.DS_Store\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.eslintcache\n.netlify\n\n# yarn v2\n.yarn/*\n!.yarn/releases\n!.yarn/plugins\n!.yarn/sdks\n!.yarn/versions"
  },
  {
    "path": "baklava/.yarnrc.yml",
    "content": "nodeLinker: node-modules\n\nplugins:\n  - path: ../.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs\n    spec: \"@yarnpkg/plugin-workspace-tools\"\n  - path: ../.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs\n    spec: \"@yarnpkg/plugin-interactive-tools\"\n\nyarnPath: ../.yarn/releases/yarn-berry.cjs"
  },
  {
    "path": "baklava/README.md",
    "content": "# Dogehouse for Desktop (Baklava)\n\nThis is the desktop application for Dogehouse.\nIt uses ElectronJS as a wrapper for [dogehouse.tv](https://dogehouse.tv) and adds other native capabilities.\n\n## Features\n\n- Notifications\n- System tray\n- Global keybinds\n- Splash screen\n- Localization\n- Discord RPC\n\n## How to run\n\n> NOTE: Windows users may have to install Visual Studio 2019 and the workload `Desktop Development with C++`.\n\n- Run `yarn install`\n- Ensure [Rust](https://www.rust-lang.org/learn/get-started) is installed\n- Install `nj-cli` by running `cargo install nj-cli`\n- Run `yarn build:globalkey`\n- Run `yarn start`\n- _(Optional)_ Run `yarn build:%YOUR_PLATFORM_CODE%` and install the app from the build _(located in `/builds`)_\n\nIf you encounter any errors while building please create a new issue for it or ask for help on the [Discord](https://discord.gg/wCbKBZF9cV0).\n\n## Using hot reload\n\nThe electron wrapper has the capability for hot reloads during development.\nYou can run the app in hot reload mode using `yarn dev`.\n\nWhile in hot reload mode, any time you save changes to a `ts` file, the electron app will recompile and relaunch itself.\nTo exit hot reload mode, use `Ctrl+C` in the console. This will however, leave the current app window open, so be sure to close that also, as it will no longer have access to hot reload.\n\n## Translations\n\nTranslations are stored in `baklava/locales` and loaded using the `i18next` library.\nYou can add new languages by creating a folder with the correct [language code](https://www.electronjs.org/docs/api/locales) and adding the `translate.json` file.\nAll keys are present in `baklava/src/generated/translationKeys.ts`, so it's a good idea to use it for reference.\n\nYou can sync the files and generate missing keys using `yarn i18`.\n"
  },
  {
    "path": "baklava/locales/en/translate.json",
    "content": "{\n  \"common\": {\n    \"title\": \"Dogehouse\"\n  },\n  \"splash\": {\n    \"check\": \"Checking for updates...\",\n    \"download\": \"Downloading Updates...\",\n    \"relaunch\": \"Relaunching...\",\n    \"launch\": \"Launching...\",\n    \"skipCheck\": \"Skipping update checks...\",\n    \"notfound\": \"No updates found...\"\n  }\n}"
  },
  {
    "path": "baklava/package.json",
    "content": "{\n    \"name\": \"dogehouse\",\n    \"version\": \"1.0.69\",\n    \"description\": \"Taking voice conversations to the moon 🚀\",\n    \"main\": \"./dist/electron.js\",\n    \"scripts\": {\n        \"compile\": \"tsc\",\n        \"watch\": \"tsc -w\",\n        \"dev\": \"yarn compile && node ./dist/dev.js\",\n        \"start\": \"yarn compile && electron ./dist/electron.js\",\n        \"gen:i18:keys\": \"ts-node --project scripts/tsconfig.json scripts/generateTranslationTypes.ts\",\n        \"sync:i18\": \"ts-node --project scripts/tsconfig.json scripts/syncTranslations.ts\",\n        \"i18\": \"npm run gen:i18:keys && npm run sync:i18\",\n        \"build:all\": \"yarn compile && electron-builder -mwl\",\n        \"build:mac\": \"yarn compile && electron-builder --mac\",\n        \"build:win\": \"yarn compile && electron-builder --win\",\n        \"build:linux\": \"yarn compile && electron-builder --linux\",\n        \"build:overlay\": \"cd resources/overlay && yarn build && cd ../../\",\n        \"build:globalkey\": \"yarn remove globalkey && yarn add globalkey && cd node_modules/globalkey && npx electron-build-env nj-cli build --release\",\n        \"delete:artifacts\": \"yarn delete:globalkey_artifacts && yarn delete:overlay_yarn_cache\",\n        \"delete:globalkey_artifacts\": \"cd node_modules/globalkey && del-cli target src && cd ../../\",\n        \"delete:overlay_yarn_cache\": \"cd resources/overlay && del-cli .yarn && cd ../../\"\n    },\n    \"keywords\": [\n        \"DogeHouse\",\n        \"Electron\",\n        \"voice-chat\",\n        \"Doge\"\n    ],\n    \"author\": \"Ben Awad <benawadapps@gmail.com>\",\n    \"homepage\": \"https://github.com/benawad/dogehouse/\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/benawad/dogehouse\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/benawad/dogehouse/issues\"\n    },\n    \"license\": \"MIT\",\n    \"build\": {\n        \"appId\": \"com.electron.dogehouse\",\n        \"productName\": \"DogeHouse\",\n        \"afterSign\": \"electron-builder-notarize\",\n        \"mac\": {\n            \"hardenedRuntime\": true,\n            \"icon\": \"icons/icon.png\",\n            \"category\": \"public.app-category.social-networking\",\n            \"entitlements\": \"settings/entitlements.mac.plist\",\n            \"extendInfo\": {\n                \"NSMicrophoneUsageDescription\": \"Mic access for speech input in voice.\"\n            },\n            \"target\": [\n                \"dmg\",\n                \"zip\"\n            ]\n        },\n        \"win\": {\n            \"icon\": \"icons/icon.png\",\n            \"target\": \"nsis\",\n            \"publish\": [\n                \"github\"\n            ]\n        },\n        \"linux\": {\n            \"icon\": \"icons/icon.png\",\n            \"target\": [\n                \"deb\",\n                \"AppImage\",\n                \"snap\",\n                \"tar.gz\",\n                \"rpm\"\n            ],\n            \"category\": \"AudioVideo\"\n        },\n        \"snap\": {\n            \"confinement\": \"strict\",\n            \"plugs\": [\n                \"default\",\n                \"audio-playback\",\n                \"audio-record\",\n                {\n                    \"browser-support\": {\n                        \"interface\": \"browser-support\",\n                        \"allow-sandbox\": false\n                    }\n                }\n            ]\n        },\n        \"extraMetadata\": {\n            \"main\": \"dist/electron.js\"\n        },\n        \"directories\": {\n            \"output\": \"./builds\"\n        },\n        \"extends\": null\n    },\n    \"devDependencies\": {\n        \"@types/discord-rpc\": \"^3.0.5\",\n        \"@types/i18next-node-fs-backend\": \"^2.1.0\",\n        \"@types/lodash\": \"^4.14.168\",\n        \"@types/node\": \"^14.14.31\",\n        \"@types/prettier\": \"^2.2.3\",\n        \"builder-util\": \"^22.10.5\",\n        \"del-cli\": \"^3.0.1\",\n        \"electron\": \"^12.0.2\",\n        \"electron-build-env\": \"^0.2.0\",\n        \"electron-builder\": \"^22.10.5\",\n        \"electron-builder-notarize\": \"^1.2.0\",\n        \"lodash\": \"^4.17.21\",\n        \"prettier\": \"^2.2.1\",\n        \"pretty-quick\": \"^3.1.0\",\n        \"ts-node\": \"^9.1.1\",\n        \"typescript\": \"^4.2.2\"\n    },\n    \"dependencies\": {\n        \"discord-rpc\": \"^3.2.0\",\n        \"dotenv\": \"^8.2.0\",\n        \"electron-log\": \"^4.3.2\",\n        \"electron-overlay-window\": \"^1.0.4\",\n        \"electron-updater\": \"^4.3.8\",\n        \"globalkey\": \"^1.0.7\",\n        \"i18next\": \"^20.0.0\",\n        \"i18next-node-fs-backend\": \"^2.1.3\"\n    },\n    \"files\": [\n        \"./icons/**/*\",\n        \"./resources/**/*\"\n    ]\n}"
  },
  {
    "path": "baklava/resources/overlay/.gitignore",
    "content": "/node_modules\n/build\n\n# yarn v2\n.yarn/*\n!.yarn/releases\n!.yarn/plugins\n!.yarn/sdks\n!.yarn/versions"
  },
  {
    "path": "baklava/resources/overlay/.yarnrc.yml",
    "content": "nodeLinker: node-modules\n\nplugins:\n  - path: ../../../.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs\n    spec: \"@yarnpkg/plugin-workspace-tools\"\n  - path: ../../../.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs\n    spec: \"@yarnpkg/plugin-interactive-tools\"\n\nyarnPath: ../../../.yarn/releases/yarn-berry.cjs"
  },
  {
    "path": "baklava/resources/overlay/README.md",
    "content": "# Getting Started with Create React App\n\nThis project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Available Scripts\n\nIn the project directory, you can run:\n\n### `yarn start`\n\nRuns the app in the development mode.\\\nOpen [http://localhost:3000](http://localhost:3000) to view it in the browser.\n\nThe page will reload if you make edits.\\\nYou will also see any lint errors in the console.\n\n### `yarn test`\n\nLaunches the test runner in the interactive watch mode.\\\nSee the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.\n\n### `yarn build`\n\nBuilds the app for production to the `build` folder.\\\nIt correctly bundles React in production mode and optimizes the build for the best performance.\n\nThe build is minified and the filenames include the hashes.\\\nYour app is ready to be deployed!\n\nSee the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.\n\n### `yarn eject`\n\n**Note: this is a one-way operation. Once you `eject`, you can’t go back!**\n\nIf you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.\n\nInstead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.\n\nYou don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.\n\n## Learn More\n\nYou can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).\n\nTo learn React, check out the [React documentation](https://reactjs.org/).\n\n### Code Splitting\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)\n\n### Analyzing the Bundle Size\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)\n\n### Making a Progressive Web App\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)\n\n### Advanced Configuration\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)\n\n### Deployment\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)\n\n### `yarn build` fails to minify\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)\n"
  },
  {
    "path": "baklava/resources/overlay/package.json",
    "content": "{\r\n  \"name\": \"dogehouse-baklava-overlay\",\r\n  \"version\": \"0.1.0\",\r\n  \"private\": true,\r\n  \"dependencies\": {\r\n    \"@testing-library/jest-dom\": \"^5.11.4\",\r\n    \"@testing-library/react\": \"^11.1.0\",\r\n    \"@testing-library/user-event\": \"^12.1.10\",\r\n    \"react\": \"^17.0.1\",\r\n    \"react-dom\": \"^17.0.1\",\r\n    \"react-icons\": \"^4.2.0\",\r\n    \"react-scripts\": \"4.0.3\",\r\n    \"web-vitals\": \"^1.0.1\"\r\n  },\r\n  \"scripts\": {\r\n    \"start\": \"set PORT=5000 && react-scripts start\",\r\n    \"build\": \"react-scripts build\",\r\n    \"test\": \"react-scripts test\",\r\n    \"eject\": \"react-scripts eject\"\r\n  },\r\n  \"eslintConfig\": {\r\n    \"extends\": [\r\n      \"react-app\",\r\n      \"react-app/jest\"\r\n    ]\r\n  },\r\n  \"browserslist\": {\r\n    \"production\": [\r\n      \">0.2%\",\r\n      \"not dead\",\r\n      \"not op_mini all\"\r\n    ],\r\n    \"development\": [\r\n      \"last 1 chrome version\",\r\n      \"last 1 firefox version\",\r\n      \"last 1 safari version\"\r\n    ]\r\n  },\r\n  \"homepage\": \"./\"\r\n}\r\n"
  },
  {
    "path": "baklava/resources/overlay/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    \n    <!--\n      manifest.json provides metadata used when your web app is installed on a\n      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/\n    -->\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>Dogehouse Overlay</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "baklava/resources/overlay/src/App.css",
    "content": ".App {\n  text-align: center;\n  background: rgba(0, 0, 0, 0);\n  /* background: #ffffff; */\n}\n\nimg{\n  border-radius: 50%;\n  width: 40px;\n  height: 40px;\n}\n\n.active-speaker{\n  border: solid #60A5FA 3px;\n  border-radius: 50%;\n  width: 40px;\n  float: left;\n  height: 40px;\n}\n\n.img-div{\n  display:inline;\n}\n\n.name-div{\n  display:inline; \n  white-space:nowrap;\n}\n\n.active-speaker-cont {\n  background: rgba(255, 255, 255, 0.2);\n  white-space:nowrap;\n  border-radius: 10%;\n  width: max-content;\n  margin: 5px;\n  padding: 2px;\n}\n\n.speaker-muted{\n  position: relative;\n  bottom: 0;\n  right: 0;\n}\n\n.left{\n  float:left;\n}"
  },
  {
    "path": "baklava/resources/overlay/src/App.js",
    "content": "import './App.css';\r\nimport { useEffect, useState } from 'react';\r\n// import { FiMicOff } from \"react-icons/fi\";\r\n\r\nconst ipcRenderer = window.require(\"electron\").ipcRenderer;\r\n\r\nfunction App() {\r\n  const [speakers, setSpeakers] = useState([]);\r\n  useEffect(() => {\r\n    ipcRenderer.send(\"@overlay/start_ipc\", true);\r\n    ipcRenderer.on(\"@overlay/overlayData\", (event, data) => {\r\n      if (data.currentRoom) {\r\n        let s = [];\r\n        data.currentRoom.users.forEach((u) => {\r\n          if (u.roomPermissions) {\r\n            if (\r\n              u.roomPermissions.isSpeaker ||\r\n              data.currentRoom.room.creatorId === u.id\r\n            ) {\r\n              u.isSpeaking = false;\r\n              u.isMuted = false;\r\n              if (data.currentRoom.activeSpeakerMap[u.id]) {\r\n                u.isSpeaking = true;\r\n              }\r\n              if (data.currentRoom.muteMap[u.id]) {\r\n                u.isMuted = true;\r\n              }\r\n              s.push(u);\r\n            }\r\n          } else {\r\n            if (data.currentRoom.room.creatorId === u.id) {\r\n              u.isSpeaking = false;\r\n              u.isMuted = false;\r\n              if (data.currentRoom.activeSpeakerMap[u.id]) {\r\n                u.isSpeaking = true;\r\n              }\r\n              if (data.currentRoom.muteMap[u.id]) {\r\n                u.isMuted = true;\r\n              }\r\n              s.push(u);\r\n            }\r\n          }\r\n\r\n          console.log(u);\r\n        });\r\n        setSpeakers(s);\r\n      }\r\n    });\r\n  }, []);\r\n\r\n  return (\r\n    <div className=\"App\" width=\"100%\">\r\n      {speakers &&\r\n        speakers.map((speaker) => (\r\n          <SpeakerIcon speaker={speaker} key={speaker.id} />\r\n        ))}\r\n    </div>\r\n  );\r\n}\r\n\r\nfunction SpeakerIcon(props) {\r\n  return (\r\n    <div>\r\n      <div\r\n        className={\r\n          props.speaker.isSpeaking ? \"active-speaker-cont left\" : \"left\"\r\n        }\r\n      >\r\n        <div className=\"img-div\">\r\n          <img\r\n            alt=\"speaker bubble\"\r\n            width=\"50px\"\r\n            height=\"50px\"\r\n            className={props.speaker.isSpeaking ? \"active-speaker\" : \"\"}\r\n            src={props.speaker.avatarUrl}\r\n          />\r\n          {/* {props.speaker.isMuted ?\r\n          <div className=\"speaker-muted\">\r\n            <FiMicOff />\r\n          </div>\r\n          : null} */}\r\n        </div>\r\n        {/* {props.speaker.isSpeaking ? <div className=\"name-div\"> <p>{props.speaker.displayName}</p> </div> : null} */}\r\n      </div>\r\n    </div>\r\n  );\r\n}\r\n\r\nexport default App;\r\n"
  },
  {
    "path": "baklava/resources/overlay/src/index.css",
    "content": "body {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n    sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n    monospace;\n}\n"
  },
  {
    "path": "baklava/resources/overlay/src/index.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n  document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"
  },
  {
    "path": "baklava/resources/overlay/src/reportWebVitals.js",
    "content": "const reportWebVitals = onPerfEntry => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n      getCLS(onPerfEntry);\n      getFID(onPerfEntry);\n      getFCP(onPerfEntry);\n      getLCP(onPerfEntry);\n      getTTFB(onPerfEntry);\n    });\n  }\n};\n\nexport default reportWebVitals;\n"
  },
  {
    "path": "baklava/resources/splash/splash-screen.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>DogeHouse</title>\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/inter-ui/3.18.0/inter.css\" rel=\"stylesheet\">\n</head>\n\n<body>\n    <style>\n        :root {\n            user-select: none;\n        }\n\n        body {\n            background-color: rgba(0, 0, 0, 0);\n        }\n\n        #progress {\n            width: 75%;\n            height: 10px;\n            background-color: #242C37;\n            border-radius: 10px;\n        }\n\n        #bar {\n            height: 10px;\n            width: 40%;\n            background-color: #FD4D4D;\n            border-radius: 10px;\n            transition: width 2s;\n        }\n    </style>\n    <div\n        style=\"background-color: #0B0E11; border-radius: 5%; height: 400px; width: 300px; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); -webkit-app-region: drag;\">\n        <img style=\"height:120px; position:fixed; top:35%; left:50%; transform:translate(-50%, -50%);\"\n            src=\"../../icons/icon.png\" alt=\"DogeHouse Logo\">\n        <h4\n            style=\"color: #B2BDCD; font-family: 'Inter'; position:fixed; top:68%; left:50%; transform:translate(-50%, -50%); width: 100%; text-align: center;\">\n        </h4>\n\n        <div id=\"progress\" style=\"position:fixed; top:83%; left:50%; transform:translate(-50%, -50%); display: none;\">\n            <div id=\"bar\"></div>\n        </div>\n\n        <script>\n            const { ipcRenderer } = require('electron');\n            const header = document.querySelector('h4');\n            let locale;\n            ipcRenderer.on('@locale/text', (ev, text) => {\n                locale = text;\n                document.title = locale.title;\n                header.textContent = locale.check;\n            });\n            ipcRenderer.on('download', () => {\n                header.textContent = locale.download;\n            });\n            ipcRenderer.on('percentage', (event, percent) => {\n                header.textContent = `${locale.download} ${JSON.stringify(percent)}%`;\n                document.getElementById('progress').style.display = 'inherit';\n                document.getElementById('bar').style.width = `${JSON.stringify(percent)}%`;\n            });\n            ipcRenderer.on('relaunch', () => {\n                header.textContent = locale.relaunch;\n            });\n            ipcRenderer.on('launch', () => {\n                header.textContent = locale.launch;\n            });\n            ipcRenderer.on('skipCheck', () => {\n                header.textContent = locale.skipCheck;\n            });\n            ipcRenderer.on('notfound', () => {\n                header.textContent = locale.notfound;\n            });\n        </script>\n    </div>\n</body>\n\n</html>"
  },
  {
    "path": "baklava/scripts/generateTranslationTypes.ts",
    "content": "import fs from \"fs\";\nimport { join } from \"path\";\nimport prettier from \"prettier\";\nimport { traverseTranslations } from \"./traverseTranslations\";\n\nconst s = `\n// this is autogenerated by running \\`npm run gen:i18:keys\\`\nexport type TranslationKeys =\n${traverseTranslations()\n\t.map((k) => `  \"${k}\"`)\n\t.join(\"|\\n\")}\n`;\n\nfs.writeFileSync(\n\tjoin(__dirname, \"../src/generated/translationKeys.ts\"),\n\tprettier.format(s, { parser: \"babel\", useTabs: true })\n);\n"
  },
  {
    "path": "baklava/scripts/syncTranslations.ts",
    "content": "// @ts-ignore\nimport config from \"../../.prettierrc.js\";\nimport english from \"../locales/en/translate.json\";\nimport * as fs from \"fs\";\nimport { join } from \"path\";\nimport prettier from \"prettier\";\nimport { traverseTranslations } from \"./traverseTranslations\";\nimport { get, set } from \"lodash\";\n\nconst paths = traverseTranslations();\n\nfs.readdirSync(join(__dirname, \"../locales\")).forEach((locale) => {\n  if (locale === \"en\") {\n    return;\n  }\n  const filename = join(\n    __dirname,\n    \"../public/locales\",\n    locale,\n    \"translation.json\"\n  );\n  let data: any;\n  try {\n    data = JSON.parse(fs.readFileSync(filename, { encoding: \"utf-8\" }));\n  } catch (err) {\n    throw new Error(`${locale}: ${err.message}`);\n  }\n  paths.forEach((p) => {\n    if (get(data, p, null) === null) {\n      set(data, p, get(english, p));\n    }\n  });\n\n  fs.writeFileSync(\n    filename,\n    prettier.format(JSON.stringify(data), {\n      parser: \"json\",\n      useTabs: true,\n      ...config,\n    })\n  );\n});\n"
  },
  {
    "path": "baklava/scripts/traverseTranslations.ts",
    "content": "import translations from \"../locales/en/translate.json\";\n\nconst keys: string[] = [];\n\ntype TranslationRecord = {\n\t[P in string]: string | TranslationRecord;\n};\n\nconst _traverseTranslations = (obj: TranslationRecord, path: string[]) => {\n\tObject.keys(obj).forEach((key) => {\n\t\tif (key.startsWith(\"_\")) {\n\t\t\treturn;\n\t\t}\n\t\tconst objOrString = obj[key];\n\t\tif (typeof objOrString === \"string\") {\n\t\t\tkeys.push([...path, key].join(\".\"));\n\t\t} else {\n\t\t\t_traverseTranslations(objOrString, [...path, key]);\n\t\t}\n\t});\n};\n\nexport const traverseTranslations = () => {\n\t_traverseTranslations(translations, []);\n\treturn keys;\n};\n"
  },
  {
    "path": "baklava/scripts/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es6\",\n\t\t\"module\": \"commonjs\",\n\t\t\"lib\": [\"dom\", \"es6\", \"es2017\", \"esnext.asynciterable\"],\n\t\t\"sourceMap\": true,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\",\n\t\t\"removeComments\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"strictFunctionTypes\": true,\n\t\t\"noImplicitThis\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"emitDecoratorMetadata\": true,\n\t\t\"experimentalDecorators\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"baseUrl\": \".\"\n\t},\n\t\"exclude\": [\"node_modules\"],\n\t\"include\": [\"./*.ts\"]\n}\n"
  },
  {
    "path": "baklava/settings/entitlements.mac.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.cs.allow-jit</key>\n    <true/>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n    <key>com.apple.security.cs.allow-dyld-environment-variables</key>\n    <true/>\n    <key>com.apple.security.device.audio-input</key>\n    <true/>\n    <key>com.apple.security.cs.disable-library-validation</key>\n    <true/>\n  </dict>\n</plist>"
  },
  {
    "path": "baklava/src/constants.ts",
    "content": "import { app, shell } from \"electron\";\nimport { autoUpdater } from \"electron-updater\";\n\nexport const isMac = process.platform === \"darwin\";\nexport const isWin = process.platform === \"win32\";\nexport const isLinux =\n  process.platform !== \"darwin\" && process.platform !== \"win32\";\nexport const REQUEST_TO_SPEAK_KEY = \"@keybind/invite\";\nexport const INVITE_KEY = \"@keybind/invite\";\nexport const MUTE_KEY = \"@keybind/mute\";\nexport const DEAF_KEY = \"@keybind/deafen\";\nexport const CHAT_KEY = \"@keybind/chat\";\nexport const PTT_KEY = \"@keybind/ptt\";\nexport const OVERLAY_KEY = \"@keybind/overlay\";\n\nexport const RPC_TRAY_OPTION_ID = \"@tray/rpc\";\n\nconst REPO_URL = \"https://github.com/benawad/dogehouse/\";\nconst DISCUSSION_URL = \"https://github.com/benawad/dogehouse/discussions\";\nconst ISSUES_URL = \"https://github.com/benawad/dogehouse/issues\";\n\nexport const ALLOWED_HOSTS = [\n  \"dogehouse.tv\",\n  \"next.dogehouse.tv\",\n  \"api.dogehouse.tv\",\n  \"github.com\",\n  \"localhost\",\n  \"staging.dogehouse.tv\",\n  \"doge-staging.stripcode.dev\",\n  \"api.twitter.com\",\n  \"twitter.com\",\n];\n\nexport const MENU_TEMPLATE: any = [\n  ...(isMac\n    ? [\n      {\n        label: app.name,\n        submenu: [\n          { role: \"about\" },\n          { type: \"separator\" },\n          { role: \"services\" },\n          { type: \"separator\" },\n          { role: \"hide\" },\n          { role: \"hideothers\" },\n          { role: \"unhide\" },\n          { type: \"separator\" },\n          { role: \"quit\" },\n        ],\n      },\n    ]\n    : []),\n  {\n    label: \"File\",\n    submenu: [isMac ? { role: \"close\" } : { role: \"quit\" }],\n  },\n  {\n    label: \"Edit\",\n    submenu: [\n      { role: \"undo\" },\n      { role: \"redo\" },\n      { type: \"separator\" },\n      { role: \"cut\" },\n      { role: \"copy\" },\n      { role: \"paste\" },\n      ...(isMac\n        ? [\n          { role: \"pasteAndMatchStyle\" },\n          { role: \"delete\" },\n          { role: \"selectAll\" },\n          { type: \"separator\" },\n          {\n            label: \"Speech\",\n            submenu: [{ role: \"startSpeaking\" }, { role: \"stopSpeaking\" }],\n          },\n        ]\n        : [{ role: \"delete\" }, { type: \"separator\" }, { role: \"selectAll\" }]),\n    ],\n  },\n  {\n    label: \"View\",\n    submenu: [\n      { role: \"reload\" },\n      { role: \"forceReload\" },\n      { role: \"toggleDevTools\" },\n      { type: \"separator\" },\n      { role: \"resetZoom\" },\n      { role: \"zoomIn\" },\n      { role: \"zoomOut\" },\n      { type: \"separator\" },\n      { role: \"togglefullscreen\" },\n    ],\n  },\n  {\n    label: \"Window\",\n    submenu: [\n      { role: \"minimize\" },\n      { role: \"zoom\" },\n      ...(isMac\n        ? [\n          { type: \"separator\" },\n          { role: \"front\" },\n          { type: \"separator\" },\n          { role: \"window\" },\n        ]\n        : [{ role: \"close\" }]),\n    ],\n  },\n  {\n    role: \"help\",\n    submenu: [\n      {\n        label: \"Learn More\",\n        click: async () => {\n          await shell.openExternal(REPO_URL);\n        },\n      },\n      {\n        label: \"Community Discussions\",\n        click: async () => {\n          await shell.openExternal(DISCUSSION_URL);\n        },\n      },\n      {\n        label: \"Search Issues\",\n        click: async () => {\n          await shell.openExternal(ISSUES_URL);\n        },\n      },\n      {\n        label: \"Check For Updates\",\n        click: async () => {\n          autoUpdater.checkForUpdatesAndNotify();\n        },\n      },\n    ],\n  },\n];\n"
  },
  {
    "path": "baklava/src/dev.ts",
    "content": "import { exec, ChildProcess } from 'child_process';\r\n\r\nlet compiler: ChildProcess;\r\nlet app: ChildProcess;\r\n\r\nlet env = process.env;\r\nenv.hotReload = 'true';\r\n\r\n/**\r\n * Get a formatted timestamp for logging\r\n * @returns {string} A formatted timestamp (HH:MM:SS)\r\n */\r\nfunction timestamp() {\r\n  return new Date().toISOString().split('T')[1].split(\".\")[0];\r\n}\r\n\r\n/**\r\n * Start the hot reloader.\r\n * Creates a connection with the typescript compiler and listens for changes.\r\n * Once changes are compiled, the electron app is relaunched.\r\n */\r\nfunction start() {\r\n  console.log(`${timestamp()} - Starting hot reload`);\r\n  compiler = exec('npm run watch');\r\n  compiler.stdout?.on('data', data => {\r\n    if (data.includes('Watching')) {\r\n      if (app) console.log(`${timestamp()} - Detected changes`);\r\n      app = exec('npm run start', {\r\n        env\r\n      });\r\n      console.log(`${timestamp()} - Launching app`);\r\n    }\r\n  });\r\n}\r\n\r\nstart();\r\n"
  },
  {
    "path": "baklava/src/electron.ts",
    "content": "import {\n  BrowserWindow,\n  app,\n  systemPreferences,\n  ipcMain,\n  globalShortcut,\n  shell,\n  Tray,\n  Menu,\n} from \"electron\";\nimport i18n from \"i18next\";\nimport Backend from \"i18next-node-fs-backend\";\nimport { autoUpdater } from \"electron-updater\";\nimport { exitApp, RegisterKeybinds } from \"./utils/keybinds\";\n\nimport { HandleVoiceTray } from \"./utils/tray\";\nimport {\n  ALLOWED_HOSTS,\n  isLinux,\n  isMac,\n  isWin,\n  MENU_TEMPLATE,\n} from \"./constants\";\nimport path from \"path\";\nimport { StartNotificationHandler } from \"./utils/notifications\";\nimport { bWindowsType } from \"./types\";\nimport electronLogger from \"electron-log\";\nimport { startRPC } from \"./utils/rpc\";\n\nlet mainWindow: BrowserWindow;\nlet tray: Tray;\nlet menu: Menu;\nlet splash: BrowserWindow;\n\nexport let bWindows: bWindowsType;\n\nexport const __prod__ = app.isPackaged;\nconst instanceLock = app.requestSingleInstanceLock();\nlet shouldShowWindow = false;\nlet windowShowInterval: NodeJS.Timeout;\nlet skipUpdateTimeout: NodeJS.Timeout;\n\ni18n.use(Backend);\n\nelectronLogger.transports.file.level = \"debug\";\nautoUpdater.logger = electronLogger;\n// just in case we have to revert to a build\nautoUpdater.allowDowngrade = true;\n\nif (isWin) app.setAppUserModelId(\"DogeHouse\");\n\nasync function localize() {\n  await i18n.init({\n    lng: app.getLocale(),\n    debug: false,\n    backend: {\n      // path where resources get loaded from\n      loadPath: path.join(__dirname, \"../locales/{{lng}}/translate.json\"),\n    },\n    interpolation: {\n      escapeValue: false,\n    },\n    saveMissing: true,\n    fallbackLng: \"en\",\n  });\n}\n\nfunction createMainWindow() {\n  mainWindow = new BrowserWindow({\n    width: 1500,\n    height: 800,\n    minWidth: 400,\n    minHeight: 600,\n    autoHideMenuBar: true,\n    webPreferences: {\n      nodeIntegration: true,\n      contextIsolation: false,\n    },\n    frame: isLinux,\n    show: false,\n  });\n\n  // applying custom menu\n  menu = Menu.buildFromTemplate(MENU_TEMPLATE);\n  Menu.setApplicationMenu(menu);\n\n  // applying custom tray\n  tray = new Tray(path.join(__dirname, `../icons/tray.png`));\n\n  if (!__prod__) {\n    mainWindow.webContents.openDevTools();\n  }\n  mainWindow.loadURL(\n    __prod__ ? `https://dogehouse.tv/` : \"http://localhost:3000\"\n  );\n\n  bWindows = {\n    main: mainWindow,\n    overlay: undefined,\n  };\n\n  mainWindow.once(\"ready-to-show\", () => {\n    shouldShowWindow = true;\n  });\n  // crashes on mac only in dev\n  // systemPreferences.askForMediaAccess(\"microphone\");\n  ipcMain.on(\"request-mic\", async (event, _serviceName) => {\n    const isAllowed: boolean = await systemPreferences.askForMediaAccess(\n      \"microphone\"\n    );\n    event.returnValue = isAllowed;\n  });\n  if (isMac) {\n    mainWindow.webContents.send(\"@alerts/permissions\", true);\n  }\n\n  // start rpc\n  startRPC();\n\n  // registers global keybinds\n  RegisterKeybinds(bWindows);\n\n  // starting the custom voice menu handler\n  HandleVoiceTray(mainWindow, tray);\n\n  // starting the noti handler\n  StartNotificationHandler();\n\n  // graceful exiting\n  mainWindow.on(\"closed\", () => {\n    globalShortcut.unregisterAll();\n    if (bWindows.overlay) {\n      bWindows.overlay.destroy();\n    }\n    mainWindow.destroy();\n  });\n\n  // handling external links\n  const handleLinks = (event: any, url: string) => {\n    let urlObj = new URL(url);\n    let urlHost = urlObj.hostname;\n    if (!ALLOWED_HOSTS.includes(urlHost)) {\n      event.preventDefault();\n      shell.openExternal(url);\n    } else {\n      if (\n        (urlHost == ALLOWED_HOSTS[3] &&\n          urlObj.pathname !== \"/login\" &&\n          urlObj.pathname !== \"/session\" &&\n          urlObj.pathname !== \"/sessions/two-factor\" &&\n          urlObj.pathname !== \"/sessions/two-factor/webauthn\") ||\n        (\n          urlHost == ALLOWED_HOSTS[8] &&\n          urlObj.pathname !== \"/account/login_verification\"\n        )\n      ) {\n        event.preventDefault();\n        shell.openExternal(url);\n      }\n    }\n  };\n  mainWindow.webContents.on(\"new-window\", handleLinks);\n  mainWindow.webContents.on(\"will-navigate\", handleLinks);\n\n  ipcMain.on(\"@dogehouse/loaded\", (event, doge) => {\n    if (isMac) mainWindow.maximize();\n  });\n  ipcMain.on(\"@app/quit\", (event, args) => {\n    mainWindow.close();\n  });\n  ipcMain.on(\"@app/maximize\", (event, args) => {\n    if (isMac) {\n      if (mainWindow.isFullScreenable()) {\n        mainWindow.setFullScreen(!mainWindow.isFullScreen());\n      }\n    } else {\n      if (mainWindow.maximizable) {\n        if (mainWindow.isMaximized()) {\n          mainWindow.unmaximize();\n        } else {\n          mainWindow.maximize();\n        }\n      }\n    }\n  });\n  ipcMain.on(\"@app/minimize\", (event, args) => {\n    if (mainWindow.minimizable) {\n      mainWindow.minimize();\n    }\n  });\n\n  ipcMain.on(\"@app/hostPlatform\", (event, args) => {\n    event.sender.send(\"@app/hostPlatform\", {\n      isLinux,\n      isMac,\n      isWin,\n    });\n  });\n}\n\nfunction createSplashWindow() {\n  splash = new BrowserWindow({\n    width: 300,\n    height: 410,\n    transparent: true,\n    frame: false,\n    resizable: false,\n    webPreferences: {\n      nodeIntegration: true,\n      contextIsolation: false,\n    },\n  });\n  splash.loadFile(\n    path.join(__dirname, \"../resources/splash/splash-screen.html\")\n  );\n  splash.webContents.on(\"did-finish-load\", () => {\n    splash.webContents.send(\"@locale/text\", {\n      title: i18n.t(\"common.title\"),\n      check: i18n.t(\"splash.check\"),\n      download: i18n.t(\"splash.download\"),\n      relaunch: i18n.t(\"splash.relaunch\"),\n      launch: i18n.t(\"splash.launch\"),\n      skipCheck: i18n.t(\"splash.skipCheck\"),\n      notfound: i18n.t(\"splash.notfound\"),\n    });\n  });\n}\n\nif (!instanceLock) {\n  if (process.env.hotReload) {\n    app.relaunch();\n  }\n  exitApp();\n} else {\n  app.on(\"ready\", () => {\n    localize().then(async () => {\n      createSplashWindow();\n      if (!__prod__) skipUpdateCheck(splash);\n      if (__prod__ && !isLinux) await autoUpdater.checkForUpdates();\n      if (isLinux && __prod__) {\n        skipUpdateCheck(splash);\n      }\n    });\n  });\n  app.on(\"second-instance\", (event, argv, workingDirectory) => {\n    if (mainWindow) {\n      if (process.env.hotReload) return mainWindow.close();\n      if (mainWindow.isMinimized()) mainWindow.restore();\n      mainWindow.focus();\n    }\n  });\n}\n\nautoUpdater.on(\"update-available\", (info) => {\n  splash.webContents.send(\"download\", info);\n  // skip the update if it takes more than 1 minute\n  skipUpdateTimeout = setTimeout(() => {\n    skipUpdateCheck(splash);\n  }, 60000);\n});\nautoUpdater.on(\"download-progress\", (progress) => {\n  let prog = Math.floor(progress.percent);\n  splash.webContents.send(\"percentage\", prog);\n  splash.setProgressBar(prog / 100);\n  // stop timeout that skips the update\n  if (skipUpdateTimeout) {\n    clearTimeout(skipUpdateTimeout);\n  }\n});\nautoUpdater.on(\"update-downloaded\", () => {\n  splash.webContents.send(\"relaunch\");\n  // stop timeout that skips the update\n  if (skipUpdateTimeout) {\n    clearTimeout(skipUpdateTimeout);\n  }\n  setTimeout(() => {\n    autoUpdater.quitAndInstall();\n  }, 1000);\n});\nautoUpdater.on(\"update-not-available\", () => {\n  skipUpdateCheck(splash);\n});\napp.on(\"window-all-closed\", async () => {\n  exitApp();\n});\napp.on(\"activate\", () => {\n  if (mainWindow === null) {\n    localize().then(() => {\n      createMainWindow();\n    });\n  }\n});\n\nfunction skipUpdateCheck(splash: BrowserWindow) {\n  createMainWindow();\n  splash.webContents.send(\"notfound\");\n  if (isLinux || !__prod__) {\n    splash.webContents.send(\"skipCheck\");\n  }\n  // stop timeout that skips the update\n  if (skipUpdateTimeout) {\n    clearTimeout(skipUpdateTimeout);\n  }\n  windowShowInterval = setInterval(() => {\n    if (shouldShowWindow) {\n      splash.webContents.send(\"launch\");\n      clearInterval(windowShowInterval);\n      setTimeout(() => {\n        splash.destroy();\n        mainWindow.show();\n      }, 800);\n    }\n  }, 1000);\n}\n"
  },
  {
    "path": "baklava/src/generated/translationKeys.ts",
    "content": "// this is autogenerated by running `npm run gen:i18:keys`\nexport type TranslationKeys =\n\t| \"common.title\"\n\t| \"splash.check\"\n\t| \"splash.download\"\n\t| \"splash.relaunch\"\n\t| \"splash.launch\";\n"
  },
  {
    "path": "baklava/src/types.ts",
    "content": "import { BrowserWindow } from \"electron\"\r\n\r\nexport type bWindowsType = {\r\n    main: BrowserWindow,\r\n    overlay: BrowserWindow | undefined\r\n}\r\n"
  },
  {
    "path": "baklava/src/utils/keybinds.ts",
    "content": "import {\n    ipcMain,\n    globalShortcut,\n    app,\n} from \"electron\";\nimport {\n    CHAT_KEY,\n    INVITE_KEY,\n    MUTE_KEY,\n    PTT_KEY,\n    REQUEST_TO_SPEAK_KEY,\n    OVERLAY_KEY,\n    isMac,\n    DEAF_KEY,\n} from \"../constants\";\nimport { overlayWindow } from \"electron-overlay-window\";\nimport { createOverlay } from \"./overlay\";\nimport { startOverlayIPCHandler } from \"./overlay/ipc\";\nimport { bWindowsType } from \"../types\";\n\nimport globalkey from 'globalkey';\nimport { stopRPC } from \"./rpc\";\n\nexport let CURRENT_REQUEST_TO_SPEAK_KEY = \"Control+8\";\nexport let CURRENT_INVITE_KEY = \"Control+7\";\nexport let CURRENT_MUTE_KEY = \"Control+m\";\nexport let CURRENT_DEAF_KEY = \"Control+1\";\nexport let CURRENT_CHAT_KEY = \"Control+9\";\nexport let CURRENT_OVERLAY_KEY = \"Control+2\";\nexport let CURRENT_PTT_KEY = [\"0\", \"Control\"];\nexport let CURRENT_PTT_KEY_STRING = \"0,control\"\n\nexport let CURRENT_APP_TITLE = \"\";\n\nlet PREV_PTT_STATUS = false;\n\nexport let worker: Worker;\n\nexport async function RegisterKeybinds(bWindows: bWindowsType) {\n    ipcMain.on(REQUEST_TO_SPEAK_KEY, (event, keyCode) => {\n        if (globalShortcut.isRegistered(CURRENT_REQUEST_TO_SPEAK_KEY)) {\n            globalShortcut.unregister(CURRENT_REQUEST_TO_SPEAK_KEY);\n        }\n        CURRENT_REQUEST_TO_SPEAK_KEY = keyCode;\n        globalShortcut.register(keyCode, () => {\n            bWindows.main.webContents.send(REQUEST_TO_SPEAK_KEY, keyCode);\n        })\n    });\n    ipcMain.on(INVITE_KEY, (event, keyCode) => {\n        if (globalShortcut.isRegistered(CURRENT_INVITE_KEY)) {\n            globalShortcut.unregister(CURRENT_INVITE_KEY);\n        }\n        CURRENT_INVITE_KEY = keyCode;\n        globalShortcut.register(keyCode, () => {\n            bWindows.main.webContents.send(INVITE_KEY, keyCode);\n        })\n    });\n    ipcMain.on(MUTE_KEY, (event, keyCode) => {\n        if (globalShortcut.isRegistered(CURRENT_MUTE_KEY)) {\n            globalShortcut.unregister(CURRENT_MUTE_KEY);\n        }\n        CURRENT_MUTE_KEY = keyCode\n        globalShortcut.register(keyCode, () => {\n            bWindows.main.webContents.send(MUTE_KEY, keyCode);\n        })\n    });\n    ipcMain.on(DEAF_KEY, (event, keyCode) => {\n        if (globalShortcut.isRegistered(CURRENT_DEAF_KEY)) {\n            globalShortcut.unregister(CURRENT_DEAF_KEY);\n        }\n        CURRENT_DEAF_KEY = keyCode\n        globalShortcut.register(keyCode, () => {\n            bWindows.main.webContents.send(DEAF_KEY, keyCode);\n        })\n    });\n    ipcMain.on(CHAT_KEY, (event, keyCode) => {\n        if (globalShortcut.isRegistered(CURRENT_CHAT_KEY)) {\n            globalShortcut.unregister(CURRENT_CHAT_KEY);\n        }\n        CURRENT_CHAT_KEY = keyCode;\n        globalShortcut.register(keyCode, () => {\n            bWindows.main.webContents.send(CHAT_KEY, keyCode);\n        })\n    });\n    ipcMain.on(PTT_KEY, (event, keyCode: string) => {\n        if (keyCode.includes(\"+\")) {\n            let keys = keyCode.split(\"+\");\n            CURRENT_PTT_KEY = keys;\n        } else {\n            CURRENT_PTT_KEY = [keyCode];\n        }\n        CURRENT_PTT_KEY = CURRENT_PTT_KEY.sort();\n        CURRENT_PTT_KEY_STRING = CURRENT_PTT_KEY.join().toLowerCase();\n    });\n\n    ipcMain.on(OVERLAY_KEY, (event, keyCode) => {\n        if (globalShortcut.isRegistered(CURRENT_OVERLAY_KEY)) {\n            globalShortcut.unregister(CURRENT_OVERLAY_KEY);\n        }\n        CURRENT_OVERLAY_KEY = keyCode;\n        globalShortcut.register(keyCode, () => {\n            if (!isMac) {\n                if (bWindows.overlay) {\n                    if (!bWindows.overlay.isVisible()) {\n                        bWindows.overlay.show();\n                        bWindows.main.webContents.send(\"@overlay/start_ipc\", true);\n                    } else {\n                        bWindows.overlay.hide();\n                        bWindows.main.webContents.send(\"@overlay/start_ipc\", true);\n                    }\n                } else {\n                    bWindows.overlay = createOverlay(CURRENT_APP_TITLE, overlayWindow);\n                    startOverlayIPCHandler(bWindows.main, bWindows.overlay);\n                }\n            }\n        })\n    });\n\n    ipcMain.on(\"@overlay/app_title\", (event, appTitle: string) => {\n        CURRENT_APP_TITLE = appTitle;\n    })\n\n    globalkey.start(\n        down => {\n            down.forEach((key: any) => {\n                let i = down.indexOf(key);\n                down[i] = down[i].replace(\"L\", \"\");\n                down[i] = down[i].replace(\"R\", \"\");\n                down[i] = down[i].replace(\"Key\", \"\");\n            });\n            down = down.sort()\n            const keyString = down.join().toLowerCase();\n            let PTT = keyString !== CURRENT_PTT_KEY_STRING;\n            if (PREV_PTT_STATUS !== PTT) {\n                bWindows.main.webContents.send(\"@voice/ptt_status_change\", PTT);\n                PREV_PTT_STATUS = PTT;\n            }\n        },\n        up => { }\n    );\n}\n\nexport function exitApp() {\n    globalkey.stop();\n    stopRPC();\n    app.quit();\n}\n"
  },
  {
    "path": "baklava/src/utils/notifications.ts",
    "content": "import {\r\n    ipcMain,\r\n    Notification\r\n} from \"electron\";\r\n\r\nexport async function StartNotificationHandler() {\r\n    if (Notification.isSupported()) {\r\n        ipcMain.on(\"@notification/mention\", (event, msg) => {\r\n            let body = \"\";\r\n            msg.tokens.forEach((token) => {\r\n                if (token.t == \"mention\") {\r\n                    body += \"@\" + token.v + \" \"\r\n                } else {\r\n                    body += token.v + \" \"\r\n                }\r\n            });\r\n            let notification = {\r\n                title: msg.displayName,\r\n                body,\r\n            }\r\n            new Notification(notification).show()\r\n        })\r\n        ipcMain.on(\"@notification/invitation\", (event, invite) => {\r\n            let notification = {\r\n                title: \"Room Invitation\",\r\n                body: `${invite.username} has invited you to ${invite.roomName}`,\r\n            }\r\n            new Notification(notification).show()\r\n        })\r\n        ipcMain.on(\"@notification/indirect_invitation\", (event, invite) => {\r\n            let notification = {\r\n                title: \"DogeHouse\",\r\n                body: `${invite.username} has just created ${invite.roomName}\\nJoin them now!`,\r\n            }\r\n            new Notification(notification).show()\r\n        })\r\n        ipcMain.on(\"@notification/mod\", (event, isMod: boolean) => {\r\n            let notification = {\r\n                title: \"DogeHouse\",\r\n                body: `You are ${isMod ? \"now\" : \"no longer\"} a mod`,\r\n            }\r\n            new Notification(notification).show()\r\n        })\r\n    }\r\n}\r\n"
  },
  {
    "path": "baklava/src/utils/overlay/index.ts",
    "content": "import { BrowserWindow } from 'electron';\nimport * as path from \"path\";\nimport { __prod__ } from '../../electron';\n\nexport function createOverlay(target: string, OW: any) {\n    const overLay = new BrowserWindow({\n        width: 400,\n        height: 300,\n        webPreferences: {\n            nodeIntegration: true,\n        },\n        ...OW.WINDOW_OPTS,\n    });\n    overLay.loadFile(path.join(__dirname, \"../../resources/overlay/build/index.html\"));\n    //overLay.loadURL(\"http://localhost:5000\");\n    OW.attachTo(overLay, target);\n    return overLay;\n}\n"
  },
  {
    "path": "baklava/src/utils/overlay/ipc.ts",
    "content": "import {\n    ipcMain,\n    BrowserWindow,\n} from \"electron\";\n\nexport async function startOverlayIPCHandler(mainWindow: BrowserWindow, overlayWindow: BrowserWindow) {\n    ipcMain.on(\"@overlay/start_ipc\", (event, data) => {\n        if (overlayWindow) {\n            mainWindow.webContents.send(\"@overlay/start_ipc\", true);\n        }\n    });\n    ipcMain.on(\"@room/data\", (event, data) => {\n        if (overlayWindow) {\n            overlayWindow.webContents.send(\"@overlay/overlayData\", data);\n        }\n    });\n}"
  },
  {
    "path": "baklava/src/utils/rpc/index.ts",
    "content": "import { Client, Presence } from \"discord-rpc\";\nimport logger from \"electron-log\";\nimport { startRPCIPCHandler, stopRPCIPCHandler } from \"./ipc\";\nimport dotenv from \"dotenv\";\n// @ts-ignore\nimport { DISCORD_CLIENT_ID } from \"../../constants\";\n\ndotenv.config();\n\nconst clientId = DISCORD_CLIENT_ID || process.env.DISCORD_CLIENT_ID;\nlet client: Client;\n\nexport let RPC_RUNNING = false;\nconst defaultData: Presence = {\n  largeImageKey: \"logo\",\n  largeImageText: \"DogeHouse\",\n  instance: true,\n};\n\nexport async function startRPC() {\n  client = new Client({ transport: \"ipc\" });\n  client.login({ clientId }).catch((e) => {\n    logger.error(e);\n  });\n\n  client.on(\"ready\", () => {\n    RPC_RUNNING = true;\n    startRPCIPCHandler();\n    setPresence({ details: \"Logging In...\" });\n  });\n}\n\nexport async function setPresence(data: Presence) {\n  if (RPC_RUNNING) {\n    client.setActivity(Object.assign(data, defaultData));\n  }\n}\n\n// added this for when there will be a electron settings page\nexport async function stopRPC() {\n  stopRPCIPCHandler();\n  client.destroy();\n  RPC_RUNNING = false;\n}\n"
  },
  {
    "path": "baklava/src/utils/rpc/ipc.ts",
    "content": "import { Presence } from \"discord-rpc\";\nimport { ipcMain } from \"electron\";\nimport { setPresence } from \"./index\";\nlet inRoom = false;\nlet PREV_PAGE_DATA: {\n  page: string;\n  opened: boolean;\n  modal: boolean;\n  data: any;\n};\nconst START_TIME = Date.now();\nconst ROOM_DATA_UPDATE_FUNC = (event, data) => {\n  if (inRoom) {\n    let voiceState = \"Muted\";\n    let isMuted = true;\n    let isDeafened = false;\n    let isSpeaker = false;\n    if (data.currentRoom) {\n      // kofta dosent send currentRoom.room\n      if (!data.currentRoom.room) {\n        data.currentRoom.room = data.currentRoom;\n      }\n      let isPrivate = data.currentRoom.room.isPrivate;\n      let meInRoom = data.currentRoom.users.find((u) => u.id == data.me.id);\n      if (meInRoom) {\n        if (meInRoom.roomPermissions) {\n          isSpeaker = meInRoom.roomPermissions.isSpeaker;\n        } else if (data.currentRoom.room.creatorId === data.me.id) {\n          isSpeaker = true;\n        }\n      }\n      if (isSpeaker) {\n        isMuted = data.muted;\n        voiceState = isMuted ? \"Muted\" : \"Unmuted\";\n      }\n      if (data.deafened) {\n        isDeafened = data.deafened;\n        voiceState = isDeafened ? \"Deafened\" : voiceState;\n      }\n      let pdata: Presence = {\n        state: isPrivate\n          ? \"In a private room\"\n          : `In ${data.currentRoom.room.name}`,\n      };\n      if (!isPrivate) {\n        pdata.details = isSpeaker\n          ? `Speaking (${data.currentRoom.users.length} of ထ)`\n          : `Listening (${data.currentRoom.users.length} of ထ)`;\n        pdata.partyId = data.currentRoom.room.id;\n        pdata.startTimestamp = new Date(data.currentRoom.room.inserted_at).getTime();\n        pdata.smallImageKey = !isDeafened ? isSpeaker && !isMuted ? \"mic_on\" : \"mic_off\" : \"speaker_off\";\n        pdata.smallImageText = isSpeaker ? `Speaker - ${voiceState}` : isDeafened ? `Listener - ${voiceState}` : `Listener`;\n        pdata.buttons = [\n          {\n            label: \"Join Room\",\n            url: `https://dogehouse.tv/room/${data.currentRoom.room.id}`,\n          },\n        ];\n      }\n      setPresence(pdata);\n    }\n  }\n};\nconst ROOM_JOINED_FUNC = (event, newinRoom) => {\n  inRoom = newinRoom;\n};\n\nconst PAGE_UPDATE_FUNC = (\n  event,\n  pageData: { page: string; opened: boolean; modal: boolean; data: any }\n) => {\n  if (!inRoom) {\n    if (pageData.opened) {\n      if (!pageData.modal) PREV_PAGE_DATA = pageData;\n      let presence: Presence = {\n        partyId: \"dogehouse69funnyhaha\",\n        startTimestamp: START_TIME,\n      };\n      switch (pageData.page) {\n        case \"home\":\n          presence.details = \"Browsing Rooms\";\n          presence.state = `${pageData.data} public rooms`;\n\n          break;\n        case \"voice-settings\":\n          presence.details = \"User Settings\";\n          presence.state = \"Editing Voice Settings\";\n          break;\n        case \"overlay-settings\":\n          presence.details = \"User Settings\";\n          presence.state = \"Editing Overlay Settings\";\n          break;\n        case \"sound-effect-settings\":\n          presence.details = \"User Settings\";\n          presence.state = \"Editing Sound Effect Settings\";\n          break;\n        case \"profile\":\n          presence.details = \"User Profile\";\n          presence.state = `Viewing @${pageData.data}`;\n          break;\n        case \"edit-profile\":\n          presence.details = \"User Settings\";\n          presence.state = \"Editing Profile\";\n          break;\n        default:\n          presence.details = \"Taking DogeHouse to the moon\";\n          break;\n      }\n      setPresence(presence);\n    } else {\n      if (PREV_PAGE_DATA) {\n        PAGE_UPDATE_FUNC(event, PREV_PAGE_DATA);\n      } else {\n        setPresence({ details: \"Taking DogeHouse to the moon\" });\n      }\n    }\n  }\n};\n\nexport async function startRPCIPCHandler() {\n  ipcMain.on(\"@rpc/page\", PAGE_UPDATE_FUNC);\n  ipcMain.on(\"@room/joined\", ROOM_JOINED_FUNC);\n  ipcMain.on(\"@room/data\", ROOM_DATA_UPDATE_FUNC);\n}\n\nexport async function stopRPCIPCHandler() {\n  ipcMain.removeListener(\"@rpc/page\", PAGE_UPDATE_FUNC);\n  ipcMain.removeListener(\"@room/joined\", ROOM_JOINED_FUNC);\n  ipcMain.removeListener(\"@room/data\", ROOM_DATA_UPDATE_FUNC);\n}\n"
  },
  {
    "path": "baklava/src/utils/tray.ts",
    "content": "import {\n    ipcMain,\n    BrowserWindow,\n    Menu,\n    Tray,\n    app\n} from \"electron\";\nimport { autoUpdater } from \"electron-updater\";\nimport { MUTE_KEY } from \"../constants\";\nimport { RPC_RUNNING, startRPC, stopRPC } from \"./rpc\";\n\nlet voiceActive = false;\n\nexport async function HandleVoiceTray(mainWindow: BrowserWindow, tray: Tray) {\n    let TRAY_MENU: any = [\n        {\n            label: \"Quit Dogehouse\",\n            click: () => {\n                mainWindow.close();\n            },\n        },\n        {\n            label: 'Toggle Mute',\n            click: () => {\n                mainWindow.webContents.send(MUTE_KEY, \"Toggled mute from Menu\");\n            }\n        },\n        {\n            label: \"Check For Updates\",\n            click: () => {\n                autoUpdater.checkForUpdatesAndNotify();\n            },\n        },\n        {\n            label: \"Toggle Discord RPC\",\n            click: () => {\n                if (RPC_RUNNING) {\n                    stopRPC();\n                } else {\n                    startRPC()\n                }\n                TRAY_MENU[3].checked = RPC_RUNNING;\n                let contextMenu = Menu.buildFromTemplate([TRAY_MENU[3], TRAY_MENU[2], seperator, TRAY_MENU[0]]);\n                if (voiceActive) {\n                    contextMenu = Menu.buildFromTemplate([TRAY_MENU[1], TRAY_MENU[3], TRAY_MENU[2], seperator, TRAY_MENU[0]]);\n                } else {\n                    contextMenu = Menu.buildFromTemplate([TRAY_MENU[3], TRAY_MENU[2], seperator, TRAY_MENU[0]]);\n                }\n                tray.setContextMenu(contextMenu);\n                tray.setContextMenu(contextMenu)\n            },\n            checked: RPC_RUNNING\n        },\n    ];\n\n    let seperator = { type: 'separator' };\n\n    // create system tray\n    tray.setToolTip(\"Taking voice conversations to the moon 🚀\");\n    tray.on(\"click\", () => {\n        mainWindow.focus();\n    });\n\n    let contextMenu = Menu.buildFromTemplate([TRAY_MENU[3], TRAY_MENU[2], seperator, TRAY_MENU[0]]);\n    tray.setContextMenu(contextMenu);\n\n    ipcMain.on(\"@room/joined\", (event, isActive: boolean) => {\n        voiceActive = isActive\n        if (voiceActive) {\n            contextMenu = Menu.buildFromTemplate([TRAY_MENU[1], TRAY_MENU[3], TRAY_MENU[2], seperator, TRAY_MENU[0]]);\n        } else {\n            contextMenu = Menu.buildFromTemplate([TRAY_MENU[3], TRAY_MENU[2], seperator, TRAY_MENU[0]]);\n        }\n        tray.setContextMenu(contextMenu);\n    });\n}\n"
  },
  {
    "path": "baklava/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"baseUrl\": \".\",\n    \"rootDir\": \"src\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"noImplicitAny\": false\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/utils/overlay/*\"\n  ]\n}"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {\n  extends: [\"@commitlint/config-conventional\"],\n  rules: {\n    \"scope-enum\": [\n      2,\n      \"always\",\n      [\n        \"global\",\n        \"baklava\",\n        \"dinner\",\n        \"kibbeh\",\n        \"kousa\",\n        \"pilaf\",\n        \"shawarma\",\n        \"kebab\",\n        \"dolma\",\n        \"globalkey\"\n      ],\n    ],\n  },\n};\n"
  },
  {
    "path": "dinner/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\"eslint:recommended\"],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 12,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint/eslint-plugin\"],\n  \"rules\": {\n    \"no-unused-vars\": \"off\",\n    \"no-empty\": \"off\"\n  }\n}\n"
  },
  {
    "path": "dinner/.gitignore",
    "content": "node_modules"
  },
  {
    "path": "dinner/package.json",
    "content": "{\n  \"name\": \"dinner\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"ts-node src/create-accounts.ts\",\n    \"audio\": \"ts-node src/play-audio.ts\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@dogehouse/kebab\": \"workspace:kebab\",\n    \"mediasoup-client\": \"^3.6.30\",\n    \"mediasoup-client-aiortc\": \"^3.6.3\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^14.14.22\",\n    \"ts-node\": \"^9.1.1\",\n    \"typescript\": \"^4.1.3\"\n  }\n}\n"
  },
  {
    "path": "dinner/src/create-accounts.ts",
    "content": "import { http, raw, wrap } from \"@dogehouse/kebab\";\nimport { Connection } from \"@dogehouse/kebab/lib/raw\";\n\nconst main = async () => {\n  const bot = http.wrap(http.create({ baseUrl: \"http://localhost:4001\" }));\n  const tokens: Array<{ accessToken: string; refreshToken: string }> = [];\n\n  for (let i = 0; i < 900; i++) {\n    console.log(i);\n    tokens.push(await bot.testUser(\"user\" + i));\n  }\n\n  const conns: Connection[] = [];\n  for (let i = 0; i < tokens.length; i++) {\n    const { accessToken, refreshToken } = tokens[i];\n    conns.push(\n      await raw.connect(accessToken, refreshToken, {\n        url: \"ws://localhost:4001/socket\",\n      })\n    );\n  }\n\n  for (const conn of conns) {\n    await wrap(conn).query.joinRoomAndGetInfo(\"\");\n  }\n};\n\nmain();\n"
  },
  {
    "path": "dinner/src/play-audio.ts",
    "content": "import { audioWrap, http, raw, wrap, Wrapper } from \"@dogehouse/kebab\";\nimport { connect as mediasoupConnect } from \"@dogehouse/kebab/lib/audio/mediasoup-client\";\nimport { Device } from \"mediasoup-client\";\nimport { createWorker, Worker } from \"mediasoup-client-aiortc\";\nimport path from \"path\";\n\nasync function makeMicTrack(\n  worker: Worker,\n  filepath = `../test-sounds/2sec.mp3`\n) {\n  const stream = await worker.getUserMedia({\n    audio: {\n      source: \"file\",\n      file: path.join(__dirname, filepath),\n    },\n  });\n  const audio = stream.getAudioTracks()[0];\n  return audio as MediaStreamTrack;\n}\n\nlet worker: Worker;\nlet device: Device;\n\nexport async function startAudio(wrapper: Wrapper) {\n  worker = await createWorker({\n    logLevel: \"warn\",\n  });\n\n  device = new Device({ handlerFactory: worker.createHandlerFactory() });\n\n  wrapAudio(wrapper);\n}\n\nexport async function wrapAudio(wrapper: Wrapper) {\n  const audioWrapper = audioWrap(wrapper.connection);\n\n  console.log(\"sub\");\n  const unsubYjap = audioWrapper.subscribe.youJoinedAsPeer(\n    async ({ routerRtpCapabilities, recvTransportOptions }) => {\n      unsubYjap();\n      console.log(\"Yjap\");\n      await mediasoupConnect(\n        wrapper.connection,\n        routerRtpCapabilities,\n        \"output\",\n        recvTransportOptions,\n        () => {}\n      )(device);\n\n      wrapper.connection.send(\"ask_to_speak\", {});\n      const unsubYbs = audioWrapper.subscribe.youBecameSpeaker(\n        async ({ sendTransportOptions }) => {\n          unsubYbs();\n          console.log(\"Ybs\");\n          // OrouterRtpCapabilities = routerRtpCapabilities;\n          // OsendTransportOptions = sendTransportOptions;\n          let track = await makeMicTrack(worker, \"../test-sounds/me.mp3\");\n          await mediasoupConnect(\n            wrapper.connection,\n            routerRtpCapabilities,\n            \"input\",\n            sendTransportOptions,\n            track\n          )(device);\n        }\n      );\n    }\n  );\n}\n\nconst test = async () => {\n  const bot = http.wrap(http.create({ baseUrl: \"http://localhost:4001\" }));\n  const { accessToken, refreshToken } = await bot.testUser(\"user1\");\n  const conn = await raw.connect(accessToken, refreshToken, {\n    url: \"http://localhost:4001/socket\",\n    logger: (direction, opcode) => {\n      if (opcode !== \"ping\") {\n        console.log(direction, opcode);\n      }\n    },\n  });\n  const wrapper = wrap(conn);\n  await startAudio(wrapper);\n  await wrapper.query.joinRoomAndGetInfo(\n    \"d34c120e-3b76-49d7-b4a9-50be27f9e9af\"\n  );\n};\n\ntest();\n"
  },
  {
    "path": "dinner/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"commonjs\",\n    \"lib\": [\n      \"dom\",\n      \"es6\",\n      \"es2017\",\n      \"esnext.asynciterable\"\n    ],\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"moduleResolution\": \"node\",\n    \"removeComments\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"strictFunctionTypes\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"resolveJsonModule\": true,\n    \"baseUrl\": \".\"\n  },\n  \"exclude\": [\n    \"node_modules\"\n  ],\n  \"include\": [\n    \"./src/**/*.tsx\",\n    \"./src/**/*.ts\"\n  ]\n}"
  },
  {
    "path": "docker-compose.local.yml",
    "content": "version: \"3\"\n\n# service_name:\n#   build: \n#     context: .\n#     dockerfile: path/to/Dockerfile\n#\n\nservices:\n  # voice server\n  shawarma:\n    build: ./shawarma\n    environment: \n      - WEBRTC_LISTEN_IP=127.0.0.1\n      - RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672\n  kofta:\n    build: ./kofta\n    # # uncomment if you want your changes to kofta code to hot reload. you'll need to npm install.\n    # volumes:\n    #   - './kofta:/usr/src/frontend'\n    labels:\n      tv.dogehouse.description: \"Frontend Service\"\n    environment: \n      - REACT_APP_API_BASE_URL=http://localhost:4001\n    ports: \n      - 3000:3000\n  # backend\n  kousa:\n    build: ./kousa\n    environment: \n      - DATABASE_URL=postgresql://postgres:postgres@postgres/postgres\n      - BEN_GITHUB_ID=benawad\n      - RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672\n      - GITHUB_CLIENT_ID=5d5xxxxxxab70\n      - GITHUB_CLIENT_SECRET=48xxxxxxxxxxxxd1cd5\n      - TWITTER_SECRET_KEY=testtwittersecret\n      - TWITER_BEARER_TOKEN=testtwitterbearer\n      - TWITTER_API_KEY=testtwitterkey\n      - ACCESS_TOKEN_SECRET=8f51exxxxxx7211dd\n      - SENTRY_DSN=SIJHFIUSDHIF\n      - SECRET_KEY_BASE=TESTING\n      - REFRESH_TOKEN_SECRET=dbb85xxxxxx9ebde\n      - WEB_URL=http://localhost:3000\n      - API_URL=http://localhost:4001\n      - PORT=4001\n    ports: \n      - 4001:4001\n  postgres:\n    environment:\n      - POSTGRES_USER=postgres\n      - POSTGRES_PASSWORD=postgres\n      - POSTGRE_DB=postgres\n  rabbitmq:\n    environment: \n      RABBITMQ_ERLANG_COOKIE: erlang_dev_only_cookie\n      RABBITMQ_DEFAULT_USER: guest\n      RABBITMQ_DEFAULT_PASS: guest\n  adminer:"
  },
  {
    "path": "docker-compose.prod.yml",
    "content": "version: \"3\"\n\n# service_name:\n#   build: \n#     context: .\n#     dockerfile: path/to/Dockerfile\n#\n\n# kofta is not here cause it's manually deployed.\nservices:\n  shawarma:\n    image: dogehouse/shawarma\n    env_file: \n      - shawarma.env\n  kousa:\n    image: dogehouse/kousa\n    env_file: \n      - kousa.env\n  postgres:\n    env_file: \n      - pg.env\n  rabbitmq:\n    env_file: \n      - rabbit.env\n  adminer:\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3\"\n\n# service_name:\n#   build: \n#     context: .\n#     dockerfile: path/to/Dockerfile\n#   restart: unless-stopped\n#   labels:\n#     tv.dogehouse.description: \"Service Description\"\n#   depends_on:\n#     - deps\n#\n\n# We will be making use of docker-compose overloads, so be sure to check the other compose files before reporting a missing field\n# On local the containers will be built locally and tagged, able to be published.\n# On prod the compose file will pull from the docker registry\nservices:\n\n  rabbitmq:\n    image: rabbitmq:3-management-alpine\n    restart: unless-stopped\n    labels:\n      tv.dogehouse.description: \"RabbitMQ broker\"\n    volumes:\n      - ./.docker/rabbitmq/data/:/var/lib/rabbitmq/\n      - ./.docker/rabbitmq/logs/:/var/log/rabbitmq/\n    ports:\n      - 5672:5672\n      - 15672:15672\n    healthcheck:\n      test: ['CMD', 'rabbitmq-diagnostics', '-q', 'ping']\n      interval: 60s\n      timeout: 5s\n\n  shawarma:\n    restart: unless-stopped\n    labels:\n      tv.dogehouse.description: \"Voice Server Service\"\n    depends_on: \n      - kousa\n\n  kousa:\n    restart: unless-stopped\n    labels:\n      tv.dogehouse.description: \"Backend Service\"\n    depends_on: \n      - postgres\n      - rabbitmq\n      \n  postgres:\n    image: postgres:13-alpine\n    restart: unless-stopped\n    labels:\n      tv.dogehouse.description: \"Postgres Database\"\n    volumes:\n      - ./.docker/postgres:/var/lib/postgresql/data\n    ports:\n      - 5432:5432\n\n  adminer:\n    image: adminer\n    restart: unless-stopped\n    ports:\n      - 8080:8080\n    labels:\n      tv.dogehouse.description: \"Adminer Database\"\n    depends_on: \n      - postgres"
  },
  {
    "path": "docs/Architecture/README.md",
    "content": "# Architecture Of DogeHouse!\n\n<img src= \"https://github.com/RonaldColyar/dogehouse/blob/staging/docs/Architecture/Architecture.png\"/>\n\n\n\n# How does DogeHouse dish out communications?\n\nVoice communications are routed directly to the node [voice server](https://github.com/benawad/dogehouse/tree/staging/shawarma) through the usage of [WebRTC](https://webrtc.org/) once the elixir API gathers credentials for connection to the node voice server through [RabbitMQ](https://www.rabbitmq.com/)!\n\n<img src=\"https://github.com/RonaldColyar/dogehouse/blob/staging/docs/Architecture/ReactAndNode.png\" />\n\n# How is data fetched?\n\nAll of data is fetched from the Client([React Front-end](https://github.com/benawad/dogehouse/tree/staging/kofta)) to the server([Elixir Api](https://github.com/benawad/dogehouse/tree/staging/kousa)) directly through a websocket connection!\n\n<img src= \"https://github.com/RonaldColyar/dogehouse/blob/staging/docs/Architecture/clientandelixr.png\" />\n\n# How is data stored?\nAll data is stored using Postgresql that is directly managed using the [Elixir Api](https://github.com/benawad/dogehouse/tree/staging/kousa)!\n\n\n<img src=\"https://github.com/RonaldColyar/dogehouse/blob/staging/docs/Architecture/ElixirAndPostgresql.png\" />\n"
  },
  {
    "path": "docs/Directory/README.md",
    "content": "# DogeHouse Code Directory\n\n\n1.Kofta(React Front End)\n  - Components\n    - [AlertModal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/AlertModal.tsx)\n    - [Backbar](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Backbar.tsx)\n    - [Blocked Users List](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/BlockedFromRoomUsers.tsx)\n    - [Bottom Room Controls](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/BottomVoiceControl.tsx)\n    - [Check Box](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Checkbox.tsx)\n    - [Circle Button](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/CircleButton.tsx)\n    - [Button](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Button.tsx)\n    - [Center Layout](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/CenterLayout.tsx)\n    - [Confirm Modal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/ConfirmModal.tsx)\n    - [Create Room Modal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/CreateRoomModal.tsx)\n    - [Create Profile Modal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/EditProfileModal.tsx)\n    - [Footer](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Footer.tsx)\n    - [Input](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Input.tsx)\n    - [Input error message](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/InputErrorMsg.tsx)\n    - [Invite Button](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/InviteButton.tsx)\n    - [Invited To Join Room Modal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/InvitedToJoinRoomModal.tsx)\n    - [Keybind Listener](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/KeybindListener.tsx)\n    - [List Item](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/ListItem.tsx)\n    - [Loading](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Loading.tsx)\n    - [Mic Permission Banner](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/MicPermissionBanner.tsx)\n    - [Modal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/Modal.tsx)\n    - [Mute Title Updater](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/MuteTitleUpdater.tsx)\n    - [Profile Button](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/ProfileButton.tsx)\n    - [Profile Modal](https://github.com/benawad/dogehouse/blob/staging/kofta/src/vscode-webview/components/ProfileModal.tsx)\n    - \n"
  },
  {
    "path": "docs/Elixir Api/README.MD",
    "content": "# Authentication\r\n\r\nTokens:\r\n\r\n- Refresh token -> lasts 30 days\r\n- Access token -> 1 hour\r\n\r\nWebsocket connection:\r\n\r\n- On initial connection if the client doesn't send an `auth` event with their refresh & access token within 8 seconds the connection will be closed by the server\r\n- If access token is valid, the user id is extracted from the payload and the user is authenticated\r\n- If the access token is invalid, the server will try to refresh both tokens\r\n\r\nRefreshing tokens:\r\n\r\n- A refresh token is invalid if\r\n    - it is malformed\r\n    - it has expired\r\n    - the token version in the payload is different then what's in the db for that user\r\n- If the refresh token is invalid, they are not authenticated and will need to log in again\r\n- If the refresh token is valid they are considered authenticated and a new refresh & access token is created/sent back to the client with the `new_tokens` event\r\n    \r\n    \r\nInvalidating tokens:\r\n\r\n- You can invalidate all refresh tokens for a user by incrementing the token version in the db\r\n"
  },
  {
    "path": "docs/README.MD",
    "content": "## Home\n<p align=\"center\">\n<img height=100 src=\"https://raw.githubusercontent.com/benawad/dogehouse/staging/.redesign-assets/dogehouse_logo.svg\"/>\n</p>\n<p align=\"center\">\n Taking voice conversations to the moon 🚀\n</p>\n\n## Welcome\nWelcome to the DogeHouse Documentation, the next voice conversation social platform.\n\nDogeHouse makes it easy to connect with friends and people over voice and chat message.\n\n### Overview\n- Dark Theme\n- Open Sign-Ups (Twitter, GitHub, and Discord)\n- Cross-Platform Support\n- Open Source\n- Text Chat\n- Powered by Doge\n\n### Basic Features\n- Create account (Twitter, GitHub, and Discord for now)\n- Create rooms and invite friends (Public/Private)\n  - When you create a room, you are automatically a mod and have a doge with glasses.\n  - You can also make people in room mod by clicking on their profile.\n  - You can edit room name, edit room description and change room visibility by clicking on the room name.\n- Schedule Room to take place at a future date\n  - Add scheduled rooms to calender\n    - Apple's Calendar\n    - Google Calendar\n    - Outlook\n    - Outlook Web App\n    - Yahoo! Calendar\n- Join room, leave room, chat in room (chat now has emojis😋), mute/unmute, deafen room, and invite\n- Mention someone in the chat with an @ (e.g. `@username`)\n- Whisper to someone in the room by using `#@username`\n- Write codeblocks using \\`backticks\\`\n- Delete message. You can only delete messages if:\n  - You wrote it\n  - You are a mod\n- Adjust volume of a speaker by clicking on their profile\n- 60+ Translations\n- Desktop App has Global Keybinds & Desktop Notifications\n- Custom Emojis\n- 🐕\n\n### Why DogeHouse?\nFor more please watch here: https://www.youtube.com/watch?v=hy-EhJ_tTQo\n"
  },
  {
    "path": "docs/React Front End/README.MD",
    "content": ""
  },
  {
    "path": "docs/Voice Server/README.MD",
    "content": ""
  },
  {
    "path": "dolma/.editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "dolma/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and *not* Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n.package-lock.json\n\nwip.ts\n.package-lock.json\n\n/lib"
  },
  {
    "path": "dolma/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 HoloPanio\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "dolma/README.md",
    "content": "<h1 align=center>\nDolma Token Transcoder<br>\n<a href=\"https://www.npmjs.com/package/@dogehouse/dolma\"><img src=\"https://img.shields.io/npm/v/@dogehouse/dolma?style=for-the-badge\"></a>\n</h1>\nThis is a token transcoder that is used to encode and decode DogeHouse chat message token arrays.  This package is written for bot/bot library developers to be able to easily transcode dogehouse chat message tokens.  \n\n## How to install\n\nTo install this, simply go to your project and run the following command:\n\n```cmd\nyarn add dolma\n```\n\n## How to use\n\nThis will show you how to encode and decode tokens\n\n### Encoding Tokens\n\nIn this example, you will see multiple ways to encode your tokens. The first one is in plain text. You can pass any string into the encoder and it will convert it into an array of Message Tokens.\n\n```ts\nimport { dolma } from \"dolma\";\n\nconst str = \"I'm @HoloPanio, and I'd like to goto `Paris, France` one day :catJAM: Also, https://dogehouse.tv is epic!\";\n\nconst tokens = dolma.encode(str);\n\nconsole.log(tokens);\n\n/**\nReturns: \n[\n  { t: 'text', v: \"I'm\" },\n  { t: 'mention', v: 'HoloPanio' },\n  { t: 'text', v: ',' },\n  { t: 'text', v: 'and' },\n  { t: 'text', v: \"I'd\" },\n  { t: 'text', v: 'like' },\n  { t: 'text', v: 'to' },\n  { t: 'text', v: 'goto' },\n  { t: 'block', v: 'Paris, France' },\n  { t: 'text', v: 'one' },\n  { t: 'text', v: 'day' },\n  { t: 'emote', v: 'catJAM' },\n  { t: 'text', v: 'Also,' },\n  { t: 'link', v: 'https://dogehouse.tv' },\n  { t: 'text', v: 'is' },\n  { t: 'text', v: 'epic!' }\n]\n*/\n```\n\nIn this example, you will see that you can have a mixed array with strings, and unitokens!  A unitoken is a token object where you define your object key as the token type, and the value as the value of the token, doing so would look like such: `{link: \"https://google.com\"}`, and this can be done for all token types.\n\n```ts\nimport { dolma } from 'dolma';\nconst arr = [\"I'm\", {mention: \"HoloPanio\"},\", and I'd like to goto\", {block: \"Paris, France\"},\"one day\", {emote: \"catJAM\"}, \"Also\",{link: 'https://dogehouse.tv'}, \"is epic!\"];\n\nconst tokens = dolma.encode(arr);\n\nconsole.log(tokens);\n\n/**\nReturns: \n[\n  { t: 'text', v: \"I'm\" },\n  { t: 'mention', v: 'HoloPanio' },\n  { t: 'text', v: ',' },\n  { t: 'text', v: 'and' },\n  { t: 'text', v: \"I'd\" },\n  { t: 'text', v: 'like' },\n  { t: 'text', v: 'to' },\n  { t: 'text', v: 'goto' },\n  { t: 'block', v: 'Paris, France' },\n  { t: 'text', v: 'one' },\n  { t: 'text', v: 'day' },\n  { t: 'emote', v: 'catJAM' },\n  { t: 'text', v: 'Also,' },\n  { t: 'link', v: 'https://dogehouse.tv' },\n  { t: 'text', v: 'is' },\n  { t: 'text', v: 'epic!' }\n]\n*/\n```\nYou can also pass in message tokens like `{t: 'link', v: 'https://dogehouse.tv'}`, and it will work because the encoder checks for all possible methods that can be used.\n\n### Decoding Tokens\n\nWhen you get a payload from DogeHouse, you can use the decode method which will take the tokens, and turn it into a raw text string when you can use anywhere you please. The decode method will always encode the data sent to it to ensure that the data is parsed correctly, so that means you can also pass in un-encoded data, such as the array in the previous example, and will print out a plain text string. In this example, we will take the array from above, and return it to a plain text string using the decode method.\n\n```ts\nimport { dolma } from \"dolma\";\n\nconst tokens = [\n  { t: 'text', v: \"I'm\" },\n  { t: 'mention', v: 'HoloPanio' },\n  { t: 'text', v: ',' },\n  { t: 'text', v: 'and' },\n  { t: 'text', v: \"I'd\" },\n  { t: 'text', v: 'like' },\n  { t: 'text', v: 'to' },\n  { t: 'text', v: 'goto' },\n  { t: 'block', v: 'Paris, France' },\n  { t: 'text', v: 'one' },\n  { t: 'text', v: 'day' },\n  { t: 'emote', v: 'catJAM' },\n  { t: 'text', v: 'Also,' },\n  { t: 'link', v: 'https://dogehouse.tv' },\n  { t: 'text', v: 'is' },\n  { t: 'text', v: 'epic!' }\n\n];\n\nconst message = dolma.decode(tokens);\n\nconsole.log(message);\n\n/**\nReturns: \n\nI'm @HoloPanio , and I'd like to goto `Paris, France` one day :catJAM: Also, https://google.com is epic!\n*/\n```"
  },
  {
    "path": "dolma/package.json",
    "content": "{\n  \"name\": \"@dogehouse/dolma\",\n  \"author\": \"Jackson Roberts <jackson@holopanio.com>\",\n  \"description\": \"A chat token transcoder for DogeHouse and associated projects.\",\n  \"version\": \"1.2.1\",\n  \"license\": \"MIT\",\n  \"main\": \"lib/index.js\",\n  \"scripts\": {\n    \"test\": \"tsc && node lib/test/run.js && tsc -b . --clean\",\n    \"clean\": \"tsc -b . --clean\",\n    \"build\": \"tsc\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/HoloPanio/dolma.git\"\n  },\n  \"keywords\": [\n    \"@dogehouse/dolma\",\n    \"dogehouse\",\n    \"doghouse-tokens\"\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/HoloPanio/dolma/issues\"\n  },\n  \"homepage\": \"https://github.com/HoloPanio/dolma#readme\",\n  \"dependencies\": {\n    \"@types/node\": \"^15.0.2\",\n    \"emoji-regex\": \"^9.2.2\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^4.2.4\"\n  }\n}\n"
  },
  {
    "path": "dolma/src/index.ts",
    "content": "import { decodeTokens } from \"./lib/decode\";\nimport { encodeTokens } from \"./lib/encode\";\nimport { Unitoken } from \"./tokens\";\nimport { MessageToken } from \"./util/types/tokenTypes\";\n\ninterface RootMethodResponse {\n\tencoded: MessageToken[],\n\tdecoded: string\n}\n\nexport * from './util/types/tokenTypes';\n\n// export default function dolma(values?: Array<Unitoken | MessageToken | string> | string): RootMethodResponse {\n// \treturn {\n// \t\tencoded: encodeTokens(values ?? \"\"),\n// \t\tdecoded: decodeTokens(values ?? \"\")\n// \t}\n// }\n\n// dolma['encode'] = encodeTokens;\n// dolma['decode'] = decodeTokens;\n\nexport default class dolma {\n  public readonly emotes: {name: string}[];\n\n  constructor(emotes: {name: string}[]) {\n    this.emotes = emotes;\n  }\n\n  public decode = decodeTokens;\n  public encode = encodeTokens;\n}\n"
  },
  {
    "path": "dolma/src/lib/decode.ts",
    "content": "import dolma from \"../index\";\nimport { Unitoken } from \"../tokens\";\nimport { MessageToken } from \"../util/types/tokenTypes\";\nimport { encodeTokens } from \"./encode\";\n\nexport function decodeTokens(this: {emotes: {name: string}[]}, all: Array<Unitoken | MessageToken | string> | string): string {\n\tconst tokens = new dolma(this.emotes).encode(all);\n\tlet vals: string[] = [];\n\n\ttokens.tokens.map(tkn => {\n\t\tif (tkn.t == 'text') return vals.push(tkn.v);\n\t\tif (tkn.t == 'block') return vals.push(`\\`${tkn.v}\\``);\n\t\tif (tkn.t == 'emote') return vals.push(`:${tkn.v}:`);\n\t\tif (tkn.t == 'mention') return vals.push(`@${tkn.v}`);\n\t\tif (tkn.t == 'link') return vals.push(tkn.v);\n\t});\n\n\tlet ret: string[] = [];\n\tconst len = vals.length\n\tvals.forEach((val, index) => {\n\t\tconst strayValues = [\",\", \".\"]\n\t\tif (strayValues.includes(val)) {\n\t\t\tret[ret.length-1] += val;\n\t\t} else {\n\t\t\tret.push(val);\n\t\t}\n\t});\n\n\treturn ret.join(' ');\n}\n"
  },
  {
    "path": "dolma/src/lib/encode.ts",
    "content": "import { Unitoken } from \"../tokens\";\nimport { MessageToken } from \"../util/types/tokenTypes\";\nimport { decodeTokens } from \"./decode\";\nimport { filterString } from \"./filterString\";\nimport { filterUnitoken } from \"./filterUnitoken\";\n\nexport function encodeTokens(this: {emotes: {name: string}[]},message: Array<Unitoken | MessageToken | string> | string) {\n\tconst tokens: MessageToken[] = [];\n\tif (!message) return {tokens: tokens, whisperedTo: []};\n\n\tif (typeof message == 'string') {\n\t\tconsole.log(this.emotes);\n\t\treturn filterString(this.emotes, message)\n\t}\n\n\t// if (typeof message == 'object') {\n\t// \tmessage.forEach((item: any, index) => {\n\t// \t\tconst unitoken = filterUnitoken(item);\n\t// \t\tconst isToken = Object.keys(item).includes('t') && Object.keys(item).includes('v');\n\n\t// \t\tif (typeof item == 'string') return filterString(item).map(tk => tokens.push(tk));\n\t// \t\tif (unitoken !== null) return tokens.push(unitoken);\n\t// \t\tif (isToken) return tokens.push(item);\n\n\t// \t\treturn;\n\t// \t})\n\t// }\n\n\treturn {tokens: tokens, whisperedTo: []};\n}\n"
  },
  {
    "path": "dolma/src/lib/filterString.ts",
    "content": "import { validationRegex } from \"../util/regex\";\nimport { MessageToken, MessageTokenType } from \"../util/types/tokenTypes\";\nimport { msgToken } from \"./msgToken\";\n\nexport function filterString(emotes: { name: string }[], message: string) {\n  const tokens: MessageToken[] = [];\n  const vals = message\n    .split(validationRegex.global)\n    .filter((e) => e != undefined && e != \"\")\n    .map((e) => e);\n  const whispers = message.split(/^\\#\\@([A-z0-9_]{4,})/gi)[1];\n\n  vals.map((e) => {\n    let tkn = msgToken.getType(e);\n    tokenSwitch:\n      switch (tkn) {\n        case \"emote\":\n          for(const emote of emotes) {\n            if(e.trim().toLowerCase() === `:${emote.name}:`) {\n              e = emote.name;\n              break tokenSwitch;\n            }\n          }\n          tkn = \"text\";\n          break;\n        case \"block\":\n          e = e.slice(1, -1);\n          break;\n        case \"mention\":\n          e = e.substr(1);\n          break;\n      }\n\n    const value = msgToken.getValue(tkn, e);\n\n    return tokens.push(msgToken.newToken(tkn, value));\n  });\n\n  return { tokens, whisperedTo: whispers ? [whispers] : [] };\n}\n"
  },
  {
    "path": "dolma/src/lib/filterUnitoken.ts",
    "content": "import { MessageToken, MessageTokenType } from \"../util/types/tokenTypes\";\nimport { msgToken } from \"./msgToken\";\n\nconst newToken = msgToken.newToken;\n\nexport function filterUnitoken(token: any): MessageToken | null {\n\tconst keys = Object.keys(token);\n\tconst tkn = msgToken.getUnitoken(token);\n\tif (keys.length > 1) return null;\n\tif (tkn == null) return null;\n\t\n\treturn newToken(tkn, token[tkn]);\n}"
  },
  {
    "path": "dolma/src/lib/msgToken.ts",
    "content": "import { validationRegex } from \"../util/regex\";\nimport { MessageToken, MessageTokenType } from \"../util/types/tokenTypes\";\n\nimport * as rawTokens from '../tokens';\n\n//@ts-ignore\nconst tokenTypes: MessageTokenType[] = Object.keys(rawTokens.default);\n\nexport function msgToken() {\n\n}\n\nmsgToken.tokens = rawTokens.default;\nmsgToken.types = tokenTypes;\n\nmsgToken.getUnitoken = (token: rawTokens.Unitoken): MessageTokenType | null => {\n\tconst utKeys = Object.keys(token);\n\tlet tkn: MessageTokenType = 'text';\n\n\ttokenTypes.map(tt => { if (tt == utKeys[0]) tkn = tt });\n\n\treturn tkn;\n}\n\nmsgToken.get = (tokenType: MessageTokenType) => {\n\tconst tkn = rawTokens.default[tokenType];\n\treturn tkn;\n}\n\nmsgToken.getType = (raw: string): MessageTokenType => {\n\tlet type: MessageTokenType = 'text';\n\ttokenTypes.forEach(tt => {\n\t\tif (msgToken.validate(tt, raw)) {\n\t\t\ttype = tt;\n\t\t}\n\t});\n\treturn type;\n}\n\nmsgToken.getValue = (tkn: MessageTokenType, raw: string): string => {\n\tconst regex = msgToken.get(tkn).regex;\n\tif (!regex) return raw;\n\telse return raw;\n}\n\nmsgToken.newToken = (tk: MessageTokenType, value: string) => {\n\tconst genToken = (t: MessageTokenType, v: string) => { return { t, v } }\n\tlet val = msgToken.get(tk).format(value);\n\treturn genToken(tk, val);\n}\n\nmsgToken.validate = (token: MessageTokenType, str: string): string | false => {\n\tconst tkn = msgToken.get(token);\n\tif (!tkn.regex) return str;\n\n\tif (str.match(tkn.regex)) return str.replace(tkn.regex, '$1');\n\telse return false;\n}\n"
  },
  {
    "path": "dolma/src/test/run.ts",
    "content": "import * as encodingTests from './tests/encoding';\nimport dolma from '../';\n\ninterface CompletedTest {\n  started: Date,\n  finished: Date,\n  type: string,\n  passed?: boolean\n}\n\nconst neutralPrefix = \"\\u001b[36m[ DOLMA ]\\u001b[0m\";\nconst successPrefix = \"\\u001b[32m[ SUCCESS ]\\u001b[0m\";\nconst failurePrefix = \"\\u001b[31m[ FAILURE ]\\u001b[0m\"\n\nconst testName = (name: string) => { return `\\u001b[37;1m(${name})\\u001b[0m` }\n\nlet failedTests: CompletedTest[] = [];\nlet successfulTests: CompletedTest[] = [];\n\nlet tests = {\n  encoding: encodingTests.default\n}\n\nfunction testFinished(test: any, started: Date, successful?: boolean) {\n  const finished = new Date();\n\n  if (successful) {\n    successfulTests.push({ started, finished, type: test.type, passed: true });\n\n    const time = ((finished.getTime() - started.getTime()) / 1000) % 60;\n    console.log(successPrefix, testName(test.name), \"Test passed in\", `${time}s!`);\n  } else {\n    failedTests.push({ started, finished, type: test.type, passed: false });\n\n    const time = ((finished.getTime() - started.getTime()) / 1000) % 60;\n    console.log(failurePrefix, testName(test.name), \"Test failed in\", `${time}s!`);\n  }\n}\nasync function main() {\n  console.log(neutralPrefix, \"Running tests on dolma...\\n\");\n\n  tests.encoding.map(async test => {\n    const started = new Date();\n    const encodedValue = new dolma([]).encode(test.input);\n    if (JSON.stringify(encodedValue) == JSON.stringify(test.expectedOutput)) testFinished(test, started, true);\n    else testFinished(test, started, false);\n  });\n\n  console.log(`\\n${neutralPrefix} Testing completed! \\u001b[37;1m(${successfulTests.length} passed) (${failedTests.length} failed)`)\n}\n\nmain();"
  },
  {
    "path": "dolma/src/test/tests/encoding.ts",
    "content": "export default [\n\t{\n\t\tname: \"Single string type test 1 (text)\",\n\t\ttype: \"encode\",\n\n\t\tinput: \"Ben Awad\",\n\t\texpectedOutput: [{ t: 'text', v: 'Ben Awad' }]\n\t},\n\n\t{\n\t\tname: \"Single string type test 2 (link)\",\n\t\ttype: \"encode\",\n\n\t\tinput: \"https://dogehouse.tv\",\n\t\texpectedOutput: [{ t: 'link', v: 'https://dogehouse.tv' }]\n\t},\n\n\t{\n\t\tname: \"Single string type test 3 (mention)\",\n\t\ttype: \"encode\",\n\n\t\tinput: \"@benawad\",\n\t\texpectedOutput: [{ t: 'mention', v: 'benawad' }]\n\t},\n\n\t{\n\t\tname: \"Single string type test 4 (block)\",\n\t\ttype: \"encode\",\n\n\t\tinput: \"`Angular.JS is horrible`\",\n\t\texpectedOutput: [{ t: 'block', v: 'Angular.JS is horrible' }]\n\t},\n\n\t{\n\t\tname: \"Single string type test 5 (emote)\",\n\t\ttype: \"encode\",\n\n\t\tinput: \":CryptoDOGE:\",\n\t\texpectedOutput: [{ t: 'emote', v: 'CryptoDOGE' }]\n\t},\n\n\t{\n\t\tname: \"Plain text encoding test 1\",\n\t\ttype: \"encode\",\n\n\t\tinput: \"This is a test to make sure that plain text tokens still work, also, https://dogehouse.tv is pretty great!\",\n\t\texpectedOutput: [\n\t\t\t{\n\t\t\t\tt: 'text',\n\t\t\t\tv: 'This is a test to make sure that plain text tokens still work, also,'\n\t\t\t},\n\t\t\t{ t: 'link', v: 'https://dogehouse.tv' },\n\t\t\t{ t: 'text', v: 'is pretty great!' }\n\t\t]\n\t},\n\n\t{\n\t\tname: \"Plain text encoding test 2\",\n\t\ttype: \"encode\",\n\n\t\tinput: \"I think Ben Awad being on simptok is super funny, but I think he would do better being a simp on dogehouse :reddogehouse:, but https://github.com/ is epic!\",\n\t\texpectedOutput: [\n\t\t\t{\n\t\t\t\tt: 'text',\n\t\t\t\tv: 'I think Ben Awad being on simptok is super funny, but I think he would do better being a simp on dogehouse'\n\t\t\t},\n\t\t\t{ t: 'emote', v: 'reddogehouse' },\n\t\t\t{ t: 'text', v: ', but' },\n\t\t\t{ t: 'link', v: 'https://github.com/' },\n\t\t\t{ t: 'text', v: 'is epic!' }\n\t\t]\n\t},\n\n\t{\n\t\tname: \"Mixed unitoken and strings encoding test 1\",\n\t\ttype: \"encode\",\n\n\t\tinput: [\"Dogecoin to the moon!!!\", { emote: \"CryptoDOGE\" }],\n\t\texpectedOutput: [\n\t\t\t{ t: 'text', v: 'Dogecoin to the moon!!!' },\n\t\t\t{ t: 'emote', v: 'CryptoDOGE' }\n\t\t]\n\t}\n];"
  },
  {
    "path": "dolma/src/tokens/index.ts",
    "content": "import * as text from './types/text';\nimport * as block from './types/block';\nimport * as mention from './types/mention';\nimport * as emote from './types/emote';\nimport * as link from './types/link';\nimport * as emoji from './types/emoji';\n\nexport interface Unitoken {\n\ttext?: string\n\tblock?: string\n\tmention?: string,\n\temote?: string,\n\tlink?: string,\n}\n\nexport default {\n\ttext: text.default,\n\tblock: block.default,\n\tmention: mention.default,\n\temote: emote.default,\n\temoji: emoji.default,\n\tlink: link.default\n};\n"
  },
  {
    "path": "dolma/src/tokens/types/block.ts",
    "content": "import { Token } from \"../../util/types/tokenTypes\";\n\nexport default {\n\tname: \"block\",\n\tregex: /\\`(.*?)\\`/gi,\n\n\tformat: (val) => val,\n  validate: (raw, val) => true\n} as Token;"
  },
  {
    "path": "dolma/src/tokens/types/emoji.ts",
    "content": "import { Token } from \"../../util/types/tokenTypes\";\nimport emojiRegex from \"emoji-regex\"\n\nexport default {\n\tname: \"emoji\",\n\tregex: emojiRegex(),\n\n\tformat: (val) => val,\n  validate: (raw, val) => true\n} as Token;\n"
  },
  {
    "path": "dolma/src/tokens/types/emote.ts",
    "content": "import { Token } from \"../../util/types/tokenTypes\";\n\nexport default {\n\tname: \"emote\",\n\tregex: /\\:([a-z0-9]+)\\:/gi,\n\n\tformat: (val) => val,\n  validate: (raw, val) => true\n} as Token;\n"
  },
  {
    "path": "dolma/src/tokens/types/link.ts",
    "content": "import { Token } from \"../../util/types/tokenTypes\";\n\nexport default {\n\tname: \"link\",\n\tregex: /(https?\\:\\/\\/[^ ]+)/gi,\n\n\tformat: (val) => val,\n  validate: (raw, val) => true\n} as Token;"
  },
  {
    "path": "dolma/src/tokens/types/mention.ts",
    "content": "import { Token } from \"../../util/types/tokenTypes\";\n\nexport default {\n\tname: \"mention\",\n\tregex: /\\@([a-zA-Z0-9_]{4,})/gi,\n\n\tformat: (val) => val,\n  validate: (raw, val) => true\n} as Token;"
  },
  {
    "path": "dolma/src/tokens/types/text.ts",
    "content": "import { Token } from \"../../util/types/tokenTypes\";\n\nexport default {\n\tname: \"text\",\n\n\tformat: (val) => val,\n  validate: (raw, val) => true \n} as Token;"
  },
  {
    "path": "dolma/src/util/regex.ts",
    "content": "export const validationRegex = {\n\tlink: /(https?\\:\\/\\/[^ ]+)/gi,\n\tmention: /\\@([a-zA-Z0-9_]{4,})/gi,\n\temote: /\\:([a-z0-9]+)\\:/gi,\n\tblock: /\\`(.*?)\\`/gi,\n\n\tglobal: /(\\`.*?\\`)|(\\@[a-zA-Z0-9_]{4,})|(\\:[a-z0-9]+\\:)|(https?\\:\\/\\/[^ ]+)/gi,\n\n\tunicodeEmoji:  /(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])/gm\n}\n"
  },
  {
    "path": "dolma/src/util/types/tokenTypes.ts",
    "content": "import * as rawTokens from '../../tokens';\n\nexport type MessageTokenType = keyof typeof rawTokens.default;\n\nexport interface MessageToken {\n\tt: MessageTokenType | string;\n\tv: string;\n}\n\nexport interface Token {\n\tname: string,\n\tregex: RegExp,\n\tformat: (value: string) => string;\n\tvalidate: (raw: string, value?: string) => boolean;\n}\n"
  },
  {
    "path": "dolma/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./lib\",\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"baseUrl\": \".\",\n    \"rootDir\": \"src\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"declaration\": true,\n    \"noImplicitAny\": false\n  },\n  \"include\": [\"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "globalkey/.gitignore",
    "content": "/target\nCargo.lock\n/.idea\n/dist\n"
  },
  {
    "path": "globalkey/Cargo.toml",
    "content": "[package]\nname = \"globalkey\"\ndescription = \"A rust node library for global key listeners in electron apps\"\nversion = \"0.1.0\"\nedition = \"2018\"\nlicense = \"MIT\"\nbuild = \"src/build.rs\"\nauthors = [\"Will Lane <williamlane923@gmail.com>\"]\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\ndevice_query = \"0.2.8\"\nlazy_static = \"1.4.0\"\nnode-bindgen = \"4.3.0\"\nstoppable_thread = \"0.2.1\"\n\n[build-dependencies]\nnode-bindgen = { version = \"4.3.0\", features = [\"build\"] }\n"
  },
  {
    "path": "globalkey/README.md",
    "content": "[![npm](https://img.shields.io/npm/v/globalkey)](https://www.npmjs.com/package/globalkey) ![downloads](https://img.shields.io/npm/dm/globalkey)\n\n# GlobalKey\n\n## Building\n\n```shell\ncargo install nj-cli\n\nnj-cli build --release\n```\n\n## Calling from node\n\n```shell\nnpm i globalkey\n\n# or\n\nyarn add globalkey\n```\n\n```node\nconst globalkey = require('globalkey');\n\nglobalkey\n    .start(x => console.log(`Keydown ${x}`), y => console.log(`Keyup ${y}`));\n\n\nsetTimeout(() => globalkey.stop(), 5000)\n```\n"
  },
  {
    "path": "globalkey/index.d.ts",
    "content": "declare module \"globalkey\" {\n    export function start(keydown_callback: (keys: string[]) => void, keyup_callback: (keys: string[]) => void): void;\n    export function stop(): void;\n}"
  },
  {
    "path": "globalkey/package.json",
    "content": "{\n  \"name\": \"@dogehouse/globalkey\",\n  \"version\": \"1.0.7\",\n  \"main\": \"dist/index.node\",\n  \"types\": \"index.d.ts\",\n  \"repository\": \"https://github.com/willdoescode/globalkey.git\",\n  \"author\": \"Will Lane <williamlane923@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "globalkey/src/build.rs",
    "content": "fn main() {\n    node_bindgen::build::configure();\n}\n"
  },
  {
    "path": "globalkey/src/lib.rs",
    "content": "use device_query::{DeviceQuery, DeviceState};\nuse node_bindgen::derive::node_bindgen;\nuse std::sync::Mutex;\n\n#[macro_use]\nextern crate lazy_static;\n\nlazy_static! {\n    static ref STOP: Mutex<Option<stoppable_thread::StoppableHandle<()>>> = Mutex::new(None);\n}\n\n#[node_bindgen(mt)]\nfn start<F: Fn(Vec<String>) + Send + 'static, X: Fn(Vec<String>) + Send + 'static>(\n    keydown_callback: F,\n    keyup_callback: X,\n) {\n    *STOP.lock().unwrap() = Some(stoppable_thread::spawn(move |stop| {\n        let state = DeviceState::new();\n\n        let mut previous_keys = Vec::new();\n        while !stop.get() {\n            std::thread::sleep(std::time::Duration::new(0, 1000));\n            let keys = state.get_keys();\n            let state = keys != previous_keys;\n\n            if !keys.is_empty() && state {\n                keydown_callback(keys.clone().iter().map(|key| key.to_string()).collect());\n            }\n\n            if !previous_keys.is_empty() && state {\n                keyup_callback(\n                    previous_keys\n                        .clone()\n                        .iter()\n                        .filter(|x| !keys.contains(x))\n                        .map(|x| x.to_string())\n                        .collect(),\n                );\n            }\n            previous_keys = keys;\n        }\n    }));\n}\n\n#[node_bindgen]\nfn stop() {\n    STOP.lock().unwrap().take().unwrap().stop().join();\n}\n"
  },
  {
    "path": "globalkey/test.js",
    "content": "const globalkey = require('./dist');\n\nglobalkey\n    .start(x => console.log(`Keydown ${x}`), y => console.log(`Keyup ${y}`));\n\n\nsetTimeout(() => globalkey.stop(), 5000)\n"
  },
  {
    "path": "kebab/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\"eslint:recommended\", \"plugin:@typescript-eslint/recommended\"],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 12,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint/eslint-plugin\"],\n  \"rules\": {\n    \"@typescript-eslint/no-empty-interface\": \"off\",\n    \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n    \"@typescript-eslint/member-delimiter-style\": \"off\",\n    \"no-empty\": \"off\",\n    \"no-empty-pattern\": \"off\",\n    \"react/prop-types\": \"off\",\n    \"accessor-pairs\": \"error\",\n    \"array-bracket-newline\": \"error\",\n    \"array-bracket-spacing\": \"error\",\n    \"array-callback-return\": \"error\",\n    \"array-element-newline\": [\"error\", \"consistent\"],\n    \"arrow-body-style\": \"off\",\n    \"arrow-parens\": \"off\",\n    \"arrow-spacing\": \"error\",\n    \"block-scoped-var\": \"error\",\n    \"block-spacing\": \"error\",\n    \"brace-style\": \"error\",\n    \"camelcase\": \"off\",\n    \"comma-dangle\": \"off\",\n    \"comma-spacing\": \"error\",\n    \"comma-style\": \"error\",\n    \"complexity\": \"error\",\n    \"computed-property-spacing\": \"error\",\n    \"consistent-this\": \"error\",\n    \"default-case-last\": \"error\",\n    \"default-param-last\": \"error\",\n    \"dot-location\": [\"error\", \"property\"],\n    \"dot-notation\": \"error\",\n    \"eol-last\": \"error\",\n    \"eqeqeq\": \"error\",\n    \"func-call-spacing\": \"error\",\n    \"func-name-matching\": \"error\",\n    \"func-names\": \"error\",\n    \"func-style\": [\n      \"error\",\n      \"declaration\",\n      {\n        \"allowArrowFunctions\": true\n      }\n    ],\n    \"function-call-argument-newline\": [\"error\", \"consistent\"],\n    \"function-paren-newline\": \"off\",\n    \"generator-star-spacing\": \"error\",\n    \"global-require\": \"error\",\n    \"grouped-accessor-pairs\": \"error\",\n    \"guard-for-in\": \"error\",\n    \"handle-callback-err\": \"error\",\n    \"id-blacklist\": \"error\",\n    \"id-denylist\": \"error\",\n    \"id-match\": \"error\",\n    \"indent\": [\n      \"error\",\n      2,\n      {\n        \"offsetTernaryExpressions\": true,\n        \"SwitchCase\": 1\n      }\n    ],\n    \"init-declarations\": \"error\",\n    \"jsx-quotes\": \"error\",\n    \"key-spacing\": [\"error\"],\n    \"keyword-spacing\": \"off\",\n    \"line-comment-position\": \"error\",\n    \"linebreak-style\": \"error\",\n    \"lines-around-comment\": \"error\",\n    \"lines-around-directive\": \"error\",\n    \"lines-between-class-members\": \"error\",\n    \"max-classes-per-file\": \"error\",\n    \"max-depth\": \"error\",\n    \"max-len\": \"off\",\n    \"max-lines\": \"off\",\n    \"max-lines-per-function\": \"off\",\n    \"max-nested-callbacks\": \"error\",\n    \"max-statements\": \"off\",\n    \"max-statements-per-line\": \"error\",\n    \"multiline-comment-style\": \"off\",\n    \"multiline-ternary\": \"off\",\n    \"new-cap\": \"error\",\n    \"new-parens\": \"error\",\n    \"newline-after-var\": \"error\",\n    \"newline-before-return\": \"error\",\n    \"newline-per-chained-call\": \"error\",\n    \"no-alert\": \"error\",\n    \"no-array-constructor\": \"error\",\n    \"no-await-in-loop\": \"error\",\n    \"no-bitwise\": \"error\",\n    \"no-buffer-constructor\": \"error\",\n    \"no-caller\": \"error\",\n    \"no-catch-shadow\": \"error\",\n    \"no-console\": \"error\",\n    \"no-constructor-return\": \"error\",\n    \"no-continue\": \"error\",\n    \"no-div-regex\": \"error\",\n    \"no-duplicate-imports\": \"error\",\n    \"no-eq-null\": \"error\",\n    \"no-eval\": \"error\",\n    \"no-extend-native\": \"error\",\n    \"no-extra-bind\": \"error\",\n    \"no-extra-label\": \"error\",\n    \"no-extra-parens\": \"off\",\n    \"no-floating-decimal\": \"error\",\n    \"no-implicit-coercion\": \"error\",\n    \"no-implicit-globals\": \"error\",\n    \"no-implied-eval\": \"error\",\n    \"no-inline-comments\": \"error\",\n    \"no-invalid-this\": \"error\",\n    \"no-iterator\": \"error\",\n    \"no-label-var\": \"error\",\n    \"no-labels\": \"error\",\n    \"no-lone-blocks\": \"error\",\n    \"no-lonely-if\": \"error\",\n    \"no-loop-func\": \"error\",\n    \"no-loss-of-precision\": \"error\",\n    \"@typescript-eslint/no-magic-numbers\": \"off\",\n    \"no-mixed-operators\": \"off\",\n    \"no-mixed-requires\": \"error\",\n    \"no-multi-assign\": \"error\",\n    \"no-multi-spaces\": \"error\",\n    \"no-multi-str\": \"error\",\n    \"no-multiple-empty-lines\": \"error\",\n    \"no-native-reassign\": \"error\",\n    \"no-negated-condition\": \"error\",\n    \"no-negated-in-lhs\": \"error\",\n    \"no-nested-ternary\": \"error\",\n    \"no-new\": \"error\",\n    \"no-new-func\": \"error\",\n    \"no-new-object\": \"error\",\n    \"no-new-require\": \"error\",\n    \"no-new-wrappers\": \"error\",\n    \"no-nonoctal-decimal-escape\": \"error\",\n    \"no-octal-escape\": \"error\",\n    \"no-param-reassign\": \"error\",\n    \"no-path-concat\": \"error\",\n    \"no-plusplus\": \"error\",\n    \"no-process-env\": \"off\",\n    \"no-process-exit\": \"error\",\n    \"no-promise-executor-return\": \"error\",\n    \"no-proto\": \"error\",\n    \"no-restricted-exports\": \"error\",\n    \"no-restricted-globals\": \"error\",\n    \"no-restricted-imports\": \"error\",\n    \"no-restricted-modules\": \"error\",\n    \"no-restricted-properties\": \"error\",\n    \"no-restricted-syntax\": \"error\",\n    \"no-script-url\": \"error\",\n    \"no-self-compare\": \"error\",\n    \"no-sequences\": \"error\",\n    \"@typescript-eslint/no-shadow\": \"error\",\n    \"no-spaced-func\": \"error\",\n    \"no-sync\": \"error\",\n    \"no-tabs\": \"error\",\n    \"no-template-curly-in-string\": \"error\",\n    \"no-throw-literal\": \"error\",\n    \"no-trailing-spaces\": \"error\",\n    \"no-undef-init\": \"error\",\n    \"no-undefined\": \"off\",\n    \"no-underscore-dangle\": \"error\",\n    \"no-unmodified-loop-condition\": \"error\",\n    \"no-unneeded-ternary\": \"error\",\n    \"no-unreachable-loop\": \"error\",\n    \"no-unsafe-optional-chaining\": \"error\",\n    \"no-unused-expressions\": \"error\",\n    \"@typescript-eslint/no-use-before-define\": \"error\",\n    \"no-useless-backreference\": \"error\",\n    \"no-useless-call\": \"error\",\n    \"no-useless-computed-key\": \"error\",\n    \"no-useless-concat\": \"error\",\n    \"no-useless-constructor\": \"error\",\n    \"no-useless-rename\": \"error\",\n    \"no-useless-return\": \"error\",\n    \"no-var\": \"error\",\n    \"no-void\": \"error\",\n    \"no-warning-comments\": \"error\",\n    \"no-whitespace-before-property\": \"error\",\n    \"nonblock-statement-body-position\": \"error\",\n    \"object-curly-newline\": \"error\",\n    \"object-curly-spacing\": [\"error\", \"always\"],\n    \"object-property-newline\": [\n      \"error\",\n      {\n        \"allowAllPropertiesOnSameLine\": true\n      }\n    ],\n    \"object-shorthand\": \"error\",\n    \"one-var\": [\"error\", \"never\"],\n    \"operator-assignment\": \"error\",\n    \"operator-linebreak\": \"error\",\n    \"padded-blocks\": [\"error\", \"never\"],\n    \"padding-line-between-statements\": \"error\",\n    \"prefer-arrow-callback\": \"error\",\n    \"prefer-const\": \"error\",\n    \"prefer-destructuring\": \"error\",\n    \"prefer-exponentiation-operator\": \"error\",\n    \"prefer-named-capture-group\": \"off\",\n    \"prefer-numeric-literals\": \"error\",\n    \"prefer-object-spread\": \"error\",\n    \"prefer-promise-reject-errors\": \"error\",\n    \"prefer-reflect\": \"error\",\n    \"prefer-regex-literals\": \"error\",\n    \"prefer-rest-params\": \"error\",\n    \"prefer-spread\": \"error\",\n    \"prefer-template\": \"off\",\n    \"quote-props\": \"off\",\n    \"quotes\": \"off\",\n    \"radix\": \"error\",\n    \"require-atomic-updates\": \"error\",\n    \"require-await\": \"error\",\n    \"require-unicode-regexp\": \"off\",\n    \"rest-spread-spacing\": \"error\",\n    \"@typescript-eslint/semi\": [\n      \"error\",\n      \"always\",\n      {\n        \"omitLastInOneLineBlock\": true\n      }\n    ],\n    \"semi-spacing\": \"error\",\n    \"semi-style\": \"error\",\n    \"no-extra-semi\": \"error\",\n    \"space-before-blocks\": \"error\",\n    \"space-before-function-paren\": [\n      \"error\",\n      {\n        \"anonymous\": \"never\",\n        \"named\": \"never\",\n        \"asyncArrow\": \"always\"\n      }\n    ],\n    \"space-in-parens\": [\"error\", \"never\"],\n    \"space-infix-ops\": \"error\",\n    \"space-unary-ops\": [\n      \"error\",\n      {\n        \"words\": true,\n        \"nonwords\": false\n      }\n    ],\n    \"spaced-comment\": \"error\",\n    \"strict\": \"error\",\n    \"switch-colon-spacing\": \"error\",\n    \"symbol-description\": \"error\",\n    \"template-curly-spacing\": \"error\",\n    \"template-tag-spacing\": \"error\",\n    \"unicode-bom\": \"error\",\n    \"valid-jsdoc\": \"error\",\n    \"vars-on-top\": \"error\",\n    \"wrap-iife\": \"error\",\n    \"wrap-regex\": \"error\",\n    \"yield-star-spacing\": \"error\",\n    \"yoda\": \"error\",\n    \"@typescript-eslint/ban-types\": \"warn\",\n    \"react/react-in-jsx-scope\": \"off\",\n    \"@typescript-eslint/no-empty-function\": \"off\",\n    \"@typescript-eslint/ban-ts-comment\": \"warn\"\n  }\n}\n"
  },
  {
    "path": "kebab/.gitignore",
    "content": ".idea\nexamples/*/.env\nexamples/*/node_modules\nexamples/*/build\nexamples/*/.parcel-cache\nexamples/*/dist\nnode_modules\nlib\n"
  },
  {
    "path": "kebab/.prettierrc.js",
    "content": "module.exports = {\n  trailingComma: \"es5\",\n  tabWidth: 2,\n  semi: true,\n  singleQuote: false,\n  arrowParens: \"always\",\n  useTabs: false,\n};\n"
  },
  {
    "path": "kebab/README.md",
    "content": "# Kebab\nThe official DogeHouse API client.\n\n## Usage\n- **In web =>** see `examples/mediasoup-audio/` and `examples/react-chat/`\n- **In Node =>** see `examples/chat/` and `examples/bot/` (note that you need to have DOM in tsc's libs)\n\n### A simple bot\n```typescript\nimport { raw, createClient, httpRequest, httpEndpoint, tokensToString, stringToToken } from \"@dogehouse/kebab\";\n\nconst commandRegex = /^\\/([^ ]+) ?(.*)$/;\nconst main = async () => {\n  try {\n    const credentials = await httpRequest(httpEndpoint.bot.auth, { apiKey: process.env.DOGEHOUSE_API_KEY! });\n    const client = createClient(await raw.connect(\n      credentials.accessToken,\n      credentials.refreshToken,\n      {\n        onConnectionTaken: () => {\n          console.error(\"\\nAnother client has taken the connection\");\n          process.exit();\n        }\n      }\n    ));\n\n    const sendMessage = (text: string) => client.request(\n      \"chat:send_msg\",\n      {\n        tokens: stringToToken(text),\n        whisperedTo: []\n      }\n    );\n\n    const { rooms } = await client.request(\"room:get_top\", { cursor: 0, limit: 1 });\n    const theRoom = rooms[0];\n\n    client.subscribe(\"new_chat_msg\", async ({ userId, msg }) => {\n      const text = tokensToString(msg.tokens);\n\n      console.log(`${msg.displayName} > ${text}`);\n      if (userId === client.user.id) return;\n\n      const [, command, parameters] = commandRegex.exec(text) ?? [\"\", \"\"];\n\n      switch (command) {\n        case \"help\":\n          await sendMessage(\"Commands: /help, /goto (owner only), /to_base64 <text>, /from_base64 <buffer>\");\n          break;\n        case \"goto\":\n          if (msg.username !== process.env.OWNER_USERRNAME || parameters.length == 0) break;\n\n          await client.request(\"room:leave\", {});\n          await client.request(\"room:join\", { roomId: parameters });\n          break;\n        case \"to_base64\":\n          if (parameters.length == 0) break;\n\n          await sendMessage(Buffer.from(parameters, \"utf-8\").toString(\"base64\"));\n\n          break;\n        case \"from_base64\":\n          if (parameters.length == 0) break;\n\n          await sendMessage(Buffer.from(parameters, \"base64\").toString(\"utf-8\"));\n\n          break;\n      }\n    });\n\n    console.info(`=> starting in room \"${theRoom.name}\" (${theRoom.numPeopleInside} people)`);\n    await client.request(\"room:join\", { roomId: theRoom.id });\n  } catch (e) {\n    if (e.code === 4001) console.error(\"invalid token!\");\n    console.error(e)\n  }\n};\n\nmain();\n```\n"
  },
  {
    "path": "kebab/examples/bot/README.md",
    "content": "# An example bot\n\n1. Put your bot's api key in `.env` as `DOGEHOUSE_API_KEY`\n2. Build the API package: `$ yarn` and `$ yarn build` in kebab's root directory\n3. Build the example: `$ yarn` and `$ yarn build`\n4. `$ yarn start`\n"
  },
  {
    "path": "kebab/examples/bot/package.json",
    "content": "{\n  \"name\": \"example-bot\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node build/index.js\"\n  },\n  \"dependencies\": {\n    \"@dogehouse/kebab\": \"file:../..\",\n    \"@types/node\": \"^14.14.35\"\n  },\n  \"devDependencies\": {\n    \"dotenv\": \"^8.2.0\",\n    \"typescript\": \"^4.2.3\"\n  }\n}\n"
  },
  {
    "path": "kebab/examples/bot/src/index.ts",
    "content": "require(\"dotenv\").config();\n\nimport { raw, createClient, httpRequest, httpEndpoint, tokensToString, stringToToken } from \"@dogehouse/kebab\";\n\nconst commandRegex = /^\\/([^ ]+) ?(.*)$/;\nconst main = async () => {\n  try {\n    const credentials = await httpRequest(httpEndpoint.bot.auth, { apiKey: process.env.DOGEHOUSE_API_KEY! });\n    const client = createClient(await raw.connect(\n      credentials.accessToken,\n      credentials.refreshToken,\n      {\n        onConnectionTaken: () => {\n          console.error(\"\\nAnother client has taken the connection\");\n          process.exit();\n        }\n      }\n    ));\n\n    const sendMessage = (text: string) => client.request(\n      \"chat:send_msg\",\n      {\n        tokens: stringToToken(text),\n        whisperedTo: []\n      }\n    );\n\n    const { rooms } = await client.request(\"room:get_top\", { cursor: 0, limit: 1 });\n    const theRoom = rooms[0];\n\n    client.subscribe(\"new_chat_msg\", async ({ userId, msg }) => {\n      const text = tokensToString(msg.tokens);\n\n      console.log(`${msg.displayName} > ${text}`);\n      if (userId === client.user.id) return;\n\n      const [, command, parameters] = commandRegex.exec(text) ?? [\"\", \"\"];\n\n      switch (command) {\n        case \"help\":\n          await sendMessage(\"Commands: /help, /goto (owner only), /to_base64 <text>, /from_base64 <buffer>\");\n          break;\n        case \"goto\":\n          if (msg.username !== process.env.OWNER_USERRNAME || parameters.length == 0) break;\n\n          await client.request(\"room:leave\", {});\n          await client.request(\"room:join\", { roomId: parameters });\n          break;\n        case \"to_base64\":\n          if (parameters.length == 0) break;\n\n          await sendMessage(Buffer.from(parameters, \"utf-8\").toString(\"base64\"));\n\n          break;\n        case \"from_base64\":\n          if (parameters.length == 0) break;\n\n          await sendMessage(Buffer.from(parameters, \"base64\").toString(\"utf-8\"));\n\n          break;\n      }\n    });\n\n    console.info(`=> starting in room \"${theRoom.name}\" (${theRoom.numPeopleInside} people)`);\n    await client.request(\"room:join\", { roomId: theRoom.id });\n  } catch (e) {\n    if (e.code === 4001) console.error(\"invalid token!\");\n    console.error(e)\n  }\n};\n\nmain();\n"
  },
  {
    "path": "kebab/examples/bot/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"outDir\": \"./build\",\n    \"strict\": true,\n    \"lib\": [\"DOM\", \"ES6\"],\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "kebab/examples/chat/README.md",
    "content": "# CLI chat (an example app)\n\n1. Put your access- and refresh-token in `.env` (`DOGEHOUSE_TOKEN`, `DOGEHOUSE_REFRESH_TOKEN`)\n2. Build the API package: `$ yarn` and `$ yarn build` in kebab's root directory\n3. Build the example: `$ yarn` and `$ yarn build`\n4. `$ yarn start`\n"
  },
  {
    "path": "kebab/examples/chat/package.json",
    "content": "{\n  \"name\": \"example-chat\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node build/index.js\"\n  },\n  \"dependencies\": {\n    \"@dogehouse/kebab\": \"file:../..\",\n    \"@types/node\": \"^14.14.35\"\n  },\n  \"devDependencies\": {\n    \"dotenv\": \"^8.2.0\",\n    \"typescript\": \"^4.2.3\"\n  }\n}\n"
  },
  {
    "path": "kebab/examples/chat/src/index.ts",
    "content": "require(\"dotenv\").config();\n\nimport readline from \"readline\";\nimport { raw, wrap, tokensToString, stringToToken } from \"@dogehouse/kebab\";\n\nconst logger: raw.Logger = (direction, opcode, data, fetchId, raw) => {\n  const directionPadded = direction.toUpperCase().padEnd(3, \" \");\n  const fetchIdInfo = fetchId ? ` (fetch id ${fetchId})` : \"\";\n  console.info(`${directionPadded} \"${opcode}\"${fetchIdInfo}: ${raw}`);\n};\n\nconst main = async () => {\n  try {\n    const connection = await raw.connect(\n      process.env.DOGEHOUSE_TOKEN!,\n      process.env.DOGEHOUSE_REFRESH_TOKEN!,\n      {\n        onConnectionTaken: () => {\n          console.error(\"\\nAnother client has taken the connection\");\n          process.exit();\n        }\n      }\n    );\n\n    const wrapper = wrap(connection);\n\n    const rl = readline.createInterface({\n      input: process.stdin,\n      output: process.stdout,\n      prompt: `${connection.user.displayName} > `\n    })\n\n    const { rooms } = await wrapper.query.getTopPublicRooms();\n    const theRoom = rooms[0];\n\n    console.log(`=> joining room \"${theRoom.name}\" (${theRoom.numPeopleInside} people)`);\n    const extraInfo = await wrapper.query.joinRoomAndGetInfo(theRoom.id);\n\n    const unsubscribe = wrapper.subscribe.newChatMsg(async ({ userId, msg }) => {\n      const text = tokensToString(msg.tokens);\n      if(userId !== connection.user.id) {\n        process.stdout.cursorTo(0);\n        console.log(`${msg.displayName} > ${text}`);\n      }\n\n      rl.prompt();\n    });\n\n    rl.prompt();\n    rl.on(\"line\", async input => {\n      if(input === \"/leave\") {\n        unsubscribe();\n        await wrapper.mutation.leaveRoom();\n        console.log(\"=> left the room\");\n      } else {\n        await wrapper.mutation.sendRoomChatMsg(stringToToken(input));\n      }\n    })\n  } catch(e) {\n    if(e.code === 4001) console.error(\"invalid token!\");\n    console.error(e)\n  }\n};\n\nmain();\n"
  },
  {
    "path": "kebab/examples/chat/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"outDir\": \"./build\",\n    \"strict\": true,\n    \"lib\": [\"DOM\", \"ES6\"],\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "kebab/examples/create-bot/README.md",
    "content": "# An script to create a bot account\n\n1. Put your access and refresh-token in `.env` (`DOGEHOUSE_TOKEN`, `DOGEHOUSE_REFRESH_TOKEN`)\n2. Choose your bot's username and place it in `.env` as `DOGEHOUSE_BOT_NAME`\n3. Build the API package: `$ yarn` and `$ yarn build` in kebab's root directory\n4. Build the example: `$ yarn` and `$ yarn build`\n5. `$ yarn start`\n6. Save the api key printed to the screen in a safe place to later use with your bot.\n"
  },
  {
    "path": "kebab/examples/create-bot/package.json",
    "content": "{\n  \"name\": \"create-bot\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node build/index.js\"\n  },\n  \"dependencies\": {\n    \"@dogehouse/kebab\": \"file:../..\",\n    \"@types/node\": \"^14.14.35\"\n  },\n  \"devDependencies\": {\n    \"dotenv\": \"^8.2.0\",\n    \"typescript\": \"^4.2.3\"\n  }\n}\n"
  },
  {
    "path": "kebab/examples/create-bot/src/index.ts",
    "content": "require(\"dotenv\").config();\n\nimport { raw, wrap } from \"@dogehouse/kebab\";\n\nconst main = async () => {\n  try {\n    const wrapper = wrap(await raw.connect(\n      process.env.DOGEHOUSE_TOKEN!,\n      process.env.DOGEHOUSE_REFRESH_TOKEN!,\n      {\n        onConnectionTaken: () => {\n          console.error(\"\\nAnother client has taken the connection\");\n          process.exit();\n        }\n      }\n    ));\n\n    wrapper.mutation.userCreateBot(process.env.DOGEHOUSE_BOT_NAME!).then(res => {\n      console.log(res)\n    }).catch(err => {\n      console.error(err)\n    })\n  } catch (e) {\n    if (e.code === 4001) console.error(\"invalid token!\");\n    console.error(e)\n  }\n};\n\nmain();\n"
  },
  {
    "path": "kebab/examples/create-bot/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"outDir\": \"./build\",\n    \"strict\": true,\n    \"lib\": [\"DOM\", \"ES6\"],\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "kebab/examples/mediasoup-audio/README.md",
    "content": "# Audio-only client (an example app)\n\n1. Put your access- and refresh-token in `.env` (`DOGEHOUSE_TOKEN`, `DOGEHOUSE_REFRESH_TOKEN`)\n2. Build the API package: `$ yarn` and `$ yarn build` in kebab's root directory\n3. Setup the example: `$ yarn`\n4. `$ yarn start`\n"
  },
  {
    "path": "kebab/examples/mediasoup-audio/package.json",
    "content": "{\n  \"name\": \"mediasoup-audio\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@dogehouse/kebab\": \"../..\",\n    \"mediasoup-client\": \"^3.6.29\"\n  },\n  \"browserslist\": [\n    \"> 0.2%\",\n    \"not dead\"\n  ],\n  \"scripts\": {\n    \"start\": \"parcel serve ./src/index.html -p 3000\",\n    \"build\": \"parcel build ./src/index.html\"\n  },\n  \"devDependencies\": {\n    \"parcel\": \"^2.0.0-beta.2\",\n    \"typescript\": \"^4.2.3\"\n  }\n}\n"
  },
  {
    "path": "kebab/examples/mediasoup-audio/src/index.css",
    "content": "body {\n  margin: 0;\n}\n\n.current-role {\n  display: flex;\n  gap: 10px;\n}\n\n.cant-use-mic {\n  color: red;\n}\n"
  },
  {
    "path": "kebab/examples/mediasoup-audio/src/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Kebab example</title>\n    <link rel=\"stylesheet\" href=\"./index.css\">\n  </head>\n  <body>\n    <h1 class=\"current-role\">Loading...</h1>\n    <h2 class=\"current-room\">Loading...</h2>\n    <script type=\"module\" src=\"./index.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "kebab/examples/mediasoup-audio/src/index.ts",
    "content": "import { wrap, audioWrap, raw } from \"@dogehouse/kebab\";\nimport { connect as mediasoupConnect } from \"@dogehouse/kebab/lib/audio/mediasoup-client\";\nimport { Device } from \"mediasoup-client\";\n\nconst main = async () => {\n  const wrapper = wrap(await raw.connect(process.env.DOGEHOUSE_TOKEN!, process.env.DOGEHOUSE_REFRESH_TOKEN!, {}));\n  const audioWrapper = audioWrap(wrapper.connection);\n  const { rooms } = await wrapper.query.getTopPublicRooms()\n  const theRoom = rooms[0];\n  const device = new Device();\n\n  const currentRole = document.querySelector(\".current-role\")!;\n\n  const makeMicTrack = async () => {\n    try {\n      const mic = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n      return mic.getAudioTracks()[0];\n    } catch {\n      const cantUseMic = document.createElement(\"span\");\n\n      cantUseMic.className = \"cant-use-mic\";\n      cantUseMic.textContent = \"- can't use mic\";\n      currentRole.appendChild(cantUseMic);\n    }\n  };\n\n  const playOutput = (track: MediaStreamTrack) => {\n    const audio = new Audio();\n\n    audio.srcObject = new MediaStream([track]);\n    audio.play();\n  };\n\n  const unsubYjap = audioWrapper.subscribe.youJoinedAsPeer(async ({ routerRtpCapabilities, recvTransportOptions }) => {\n    unsubYjap();\n\n    await mediasoupConnect(\n      wrapper.connection,\n      routerRtpCapabilities,\n      \"output\",\n      recvTransportOptions,\n      playOutput\n    )(device);\n    currentRole.textContent = \"Listener\";\n\n    const button = document.createElement(\"button\");\n\n    button.textContent = \"Request to speak\";\n    button.addEventListener(\"click\", () => wrapper.connection.send(\"ask_to_speak\", {}));\n    currentRole.appendChild(button);\n\n    const unsubYbs = audioWrapper.subscribe.youBecameSpeaker(async ({ sendTransportOptions }) => {\n      unsubYbs();\n\n      await mediasoupConnect(\n        wrapper.connection,\n        routerRtpCapabilities,\n        \"input\",\n        sendTransportOptions,\n        await makeMicTrack()\n      )(device);\n\n      currentRole.removeChild(button);\n    });\n  });\n\n  const unsubYjas = audioWrapper.subscribe.youJoinedAsSpeaker(async ({\n    routerRtpCapabilities,\n    recvTransportOptions,\n    sendTransportOptions\n  }) => {\n    unsubYjas();\n\n    await mediasoupConnect(\n      wrapper.connection,\n      routerRtpCapabilities,\n      \"output\",\n      recvTransportOptions,\n      playOutput\n    )(device);\n\n    await mediasoupConnect(\n      wrapper.connection,\n      routerRtpCapabilities,\n      \"input\",\n      sendTransportOptions,\n      await makeMicTrack()\n    )(device);\n  });\n\n  const extraInfo = await wrapper.query.joinRoomAndGetInfo(theRoom.id);\n  document.querySelector(\".current-room\")!.textContent = theRoom.name;\n}\n\nmain();\n"
  },
  {
    "path": "kebab/examples/mediasoup-audio/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"sourceMap\": true,\n    \"target\": \"es6\",\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true,\n    \"noEmitOnError\": false,\n    \"resolveJsonModule\": true,\n    \"importHelpers\": true,\n    \"lib\": [\n      \"ESNext\",\n      \"DOM\"\n    ],\n    \"module\": \"commonjs\",\n    \"esModuleInterop\": true,\n    \"preserveSymlinks\": true,\n    \"typeRoots\": [\n      \"./node_modules/@types\"\n    ],\n    \"downlevelIteration\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "kebab/examples/react-chat/README.md",
    "content": "# Text-only chat (an example app)\n\n1. Build the API package: `$ yarn` and `$ yarn build` in kebab's root directory\n2. Setup the example: `$ yarn`\n3. `$ yarn start` (put `SKIP_PREFLIGHT_CHECK=true` in your .env if it crashes)\n"
  },
  {
    "path": "kebab/examples/react-chat/package.json",
    "content": "{\n  \"name\": \"react-chat\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"homepage\": \".\",\n  \"dependencies\": {\n    \"@dogehouse/kebab\": \"file:../..\",\n    \"@testing-library/jest-dom\": \"^5.11.4\",\n    \"@testing-library/react\": \"^11.1.0\",\n    \"@testing-library/user-event\": \"^12.1.10\",\n    \"@types/jest\": \"^26.0.15\",\n    \"@types/node\": \"^12.0.0\",\n    \"@types/react\": \"^17.0.0\",\n    \"@types/react-dom\": \"^17.0.0\",\n    \"react\": \"^17.0.1\",\n    \"react-dom\": \"^17.0.1\",\n    \"react-scripts\": \"4.0.3\",\n    \"typescript\": \"^4.1.2\",\n    \"web-vitals\": \"^1.0.1\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "kebab/examples/react-chat/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta\n      name=\"description\"\n      content=\"Web site created using create-react-app\"\n    />\n    <link rel=\"apple-touch-icon\" href=\"%PUBLIC_URL%/logo192.png\" />\n    <!--\n      manifest.json provides metadata used when your web app is installed on a\n      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\" />\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>React App</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "kebab/examples/react-chat/public/manifest.json",
    "content": "{\n  \"short_name\": \"React App\",\n  \"name\": \"Create React App Sample\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"logo192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"logo512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "kebab/examples/react-chat/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "kebab/examples/react-chat/src/App.css",
    "content": ".chat-messages {\n  display: flex;\n  flex-direction: column;\n  gap: 10px;\n  overflow-y: visible;\n  flex-grow: 1;\n}\n\n.chat-prompt {\n  display: flex;\n  gap: 10px;\n}\n\n.room-chat {\n  display: flex;\n  align-items: center;\n  flex-direction: column;\n  gap: 10px;\n  height: 99%;\n}\n\n.login-form {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 10px;\n  padding: 10px;\n}\n"
  },
  {
    "path": "kebab/examples/react-chat/src/App.tsx",
    "content": "import React, { createContext, useContext, useEffect, useState } from \"react\";\nimport { Message, raw, Room, stringToToken, tokensToString, wrap, Wrapper } from \"@dogehouse/kebab\";\nimport \"./App.css\";\n\nconst APIWrapperContext = createContext<Wrapper>({} as Wrapper);\n\nconst MessageView = (props: { message: Message }) => (\n  <span>\n    <strong>{props.message.displayName}</strong>: {tokensToString(props.message.tokens)}\n  </span>\n);\n\nconst ChatMessages = () => {\n  const wrapper = useContext(APIWrapperContext);\n  const [messages, setMessages] = useState<Message[]>([]);\n\n  useEffect(() => wrapper.subscribe.newChatMsg(({msg}) => setMessages(messages.concat(msg))));\n\n  return (\n    <div className=\"chat-messages\">\n      {messages.map(it => <MessageView message={it} key={it.sentAt}/>)}\n    </div>\n  );\n};\n\nconst ChatPrompt = () => {\n  const wrapper = useContext(APIWrapperContext);\n  const [message, setMessage] = useState(\"\");\n\n  const sendMessage = async () => {\n    if(message.length > 0) {\n      await wrapper.mutation.sendRoomChatMsg(stringToToken(message));\n      setMessage(\"\");\n    }\n  };\n\n  return (\n    <div className=\"chat-prompt\">\n      <input value={message} onChange={e => setMessage(e.target.value)}/>\n      <button onClick={sendMessage}>Send</button>\n    </div>\n  );\n};\n\nconst RoomChat = (props: { room: Room }) => {\n  const wrapper = useContext(APIWrapperContext);\n  const [joined, setJoined] = useState(false);\n\n  if(!joined) {\n    wrapper.query.joinRoomAndGetInfo(props.room.id).then(() => setJoined(true));\n\n    return <span>loading 3/3</span>;\n  }\n\n  return (\n    <div className=\"room-chat\">\n      <h1>{props.room.name}</h1>\n      <ChatMessages/>\n      <ChatPrompt/>\n    </div>\n  );\n};\n\nconst LoggedInPage = (props: { token: string, refreshToken: string }) => {\n  const [connection, setConnection] = useState<raw.Connection | null>(null);\n  const [publicRooms, setPublicRooms] = useState<Room[]>([]);\n\n  useEffect(() => {\n    raw.connect(props.token, props.refreshToken, {}).then(setConnection);\n  }, [props.token, props.refreshToken]);\n\n  if(!connection) {\n    return <span>loading 1/3</span>;\n  }\n\n  const wrapper = wrap(connection);\n\n  if(publicRooms.length === 0) {\n    wrapper.query.getTopPublicRooms().then(({ rooms }) => setPublicRooms(rooms));\n\n    return <span>loading 2/3</span>;\n  }\n\n  return (\n    <APIWrapperContext.Provider value={wrapper}>\n      <RoomChat room={publicRooms[0]}/>\n    </APIWrapperContext.Provider>\n  );\n};\n\nconst LogInPage = (props: { logIn: (token: string, refreshToken: string) => void }) => {\n  const [token, setToken] = useState(\"\");\n  const [refreshToken, setRefreshToken] = useState(\"\");\n  const submit = () => props.logIn(token, refreshToken);\n\n  return (\n    <form\n      className=\"login-form\"\n      onSubmit={e => {\n        e.preventDefault();\n        submit();\n      }}\n    >\n      <input\n        type=\"password\"\n        placeholder=\"Token\"\n        minLength={24}\n        value={token} onChange={({ target }) => setToken(target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"Refresh token\"\n        minLength={24}\n        value={refreshToken} onChange={({ target }) => setRefreshToken(target.value)}\n      />\n      <input type=\"submit\" value=\"Log in\"/>\n    </form>\n  );\n};\n\nexport const App = () => {\n  const [creds, setCreds] = useState({ token: \"\", refreshToken: \"\" });\n  const loggedIn = creds.token.length > 0 && creds.refreshToken.length > 0 // lmao\n\n  return loggedIn\n    ? <LoggedInPage token={creds.token} refreshToken={creds.refreshToken}/>\n    : <LogInPage\n        logIn={(newToken, newRefreshToken) =>\n          setCreds({ token: newToken, refreshToken: newRefreshToken })\n        }\n      />;\n};\n"
  },
  {
    "path": "kebab/examples/react-chat/src/index.css",
    "content": "body {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, \"Ubuntu\", sans-serif;\n}\n\nhtml, body, #root {\n  height: 100%;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, monospace;\n}\n"
  },
  {
    "path": "kebab/examples/react-chat/src/index.tsx",
    "content": "import React from \"react\";\nimport { render } from \"react-dom\";\nimport \"./index.css\";\nimport { App } from \"./App\";\n\nrender(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n  document.getElementById(\"root\")\n);\n"
  },
  {
    "path": "kebab/examples/react-chat/src/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "kebab/examples/react-chat/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "kebab/jest.config.js",
    "content": "module.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n};\n"
  },
  {
    "path": "kebab/package.json",
    "content": "{\n  \"name\": \"@dogehouse/kebab\",\n  \"author\": \"Ilya Maximov <mail@overlisted.net> (https://overlisted.net)\",\n  \"bugs\": \"https://github.com/benawad/dogehouse/issues\",\n  \"version\": \"1.0.0\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"dogehouse\"\n  ],\n  \"description\": \"The official DogeHouse API client.\",\n  \"main\": \"lib/index.js\",\n  \"types\": \"lib/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc -w\",\n    \"lint\": \"eslint src/\",\n    \"test\": \"jest\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"dependencies\": {\n    \"@types/uuid\": \"^8.3.0\",\n    \"@types/ws\": \"^7.4.2\",\n    \"isomorphic-unfetch\": \"^3.1.0\",\n    \"isomorphic-ws\": \"^4.0.1\",\n    \"mediasoup-client\": \"^3.6.30\",\n    \"reconnecting-websocket\": \"^4.4.0\",\n    \"uuid\": \"^8.3.2\",\n    \"ws\": \"^7.4.5\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^26.0.23\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.22.0\",\n    \"@typescript-eslint/parser\": \"^4.22.0\",\n    \"eslint\": \"^7.25.0\",\n    \"jest\": \"^26.6.3\",\n    \"ts-jest\": \"^26.5.5\",\n    \"typescript\": \"^4.2.4\"\n  }\n}\n"
  },
  {
    "path": "kebab/src/README.md",
    "content": "http, websocket/wrapper.ts and websocket/responses.ts are deprecated and are only kept because of kibbeh\n"
  },
  {
    "path": "kebab/src/audio/audioWrapper.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck because internet is unpredictable\n\nimport { raw } from \"..\";\nimport { RoomPeer, UUID } from \"../entities\";\nimport {\n  TransportOptions,\n  DtlsParameters,\n  ConsumerOptions,\n  RtpCapabilities,\n  RtpParameters,\n} from \"mediasoup-client/lib/types\";\n\n/**\n * Allows you to handle custom logic on websocket voice events\n */\ntype Handler<Data> = (data: Data) => void;\n\n/**\n * Creates a wrapper object that allows you to make websocket voice related calls using functions\n * @param {raw.Connection} connection - reference to the websocket connection\n * @returns {connection} Wrapper object\n */\nexport const wrap = (connection: raw.Connection) => ({\n  connection,\n\n  /**\n   * Allows you to subscribe to various pre-defined websocket voice events\n   */\n  subscribe: {\n    closeConsumer: (handler: Handler<{ producerId: UUID }>) =>\n      connection.addListener(\"close_consumer\", handler),\n    newPeerSpeaker: (\n      handler: Handler<{ peerId: UUID; consumerParameters: ConsumerOptions }>\n    ) => connection.addListener(\"new-peer-speaker\", handler),\n    youJoinedAsPeer: (\n      handler: Handler<{\n        routerRtpCapabilities: RtpCapabilities;\n        recvTransportOptions: TransportOptions;\n      }>\n    ) => connection.addListener(\"you-joined-as-peer\", handler),\n    youJoinedAsSpeaker: (\n      handler: Handler<{\n        routerRtpCapabilities: RtpCapabilities;\n        recvTransportOptions: TransportOptions;\n        sendTransportOptions: TransportOptions;\n      }>\n    ) => connection.addListener(\"you-joined-as-speaker\", handler),\n    youBecameSpeaker: (\n      handler: Handler<{ sendTransportOptions: TransportOptions }>\n    ) => connection.addListener(\"you-are-now-a-speaker\", handler),\n  },\n\n  /**\n   * Allows you to call functions that return information about the ws voice state\n   */\n  query: {\n    getConsumersParameters: (\n      rtpCapabilities: RtpCapabilities\n    ): Promise<{ consumerParametersArr: RoomPeer[] }> =>\n      connection.fetch(\n        \"@get-recv-tracks\",\n        { rtpCapabilities },\n        \"@get-recv-tracks-done\"\n      ),\n  },\n\n  /**\n   * Allows you to call functions that mutate the ws voice state\n   */\n  mutation: {\n    connectTransport: (\n      transportId: UUID,\n      direction: \"send\" | \"recv\",\n      dtlsParameters: DtlsParameters\n    ): Promise<{ error: string } | { roomId: UUID }> =>\n      connection.fetch(\n        \"@connect-transport\",\n        { transportId, direction, dtlsParameters },\n        `@connect-transport-${direction}-done`\n      ),\n    sendTrack: (\n      transportId: UUID,\n      kind: \"audio\",\n      rtpParameters: RtpParameters,\n      rtpCapabilities: RtpCapabilities,\n      paused: false,\n      appData: { mediaTag: \"cam-audio\" },\n      direction: \"send\"\n    ): Promise<{ id: UUID } | { error: string }> =>\n      connection.fetch(\n        \"@send-track\",\n        {\n          transportId,\n          kind,\n          rtpParameters,\n          rtpCapabilities,\n          paused,\n          appData,\n          direction,\n        },\n        `@send-track-${direction}-done`\n      ),\n  },\n});\n"
  },
  {
    "path": "kebab/src/audio/interface.ts",
    "content": "import { raw, UUID } from \"..\";\nimport { RtpCapabilities, TransportOptions } from \"mediasoup-client/lib/types\";\n\nexport type ConsumerPlayer = (track: MediaStreamTrack, user: UUID) => void;\nexport type ConnectFunction<Return> = <Direction extends \"input\" | \"output\">(\n  connection: raw.Connection,\n  routerRtpCapabilities: RtpCapabilities,\n  direction: Direction extends \"input\" ? \"input\" : \"output\",\n  transportOptions: TransportOptions,\n  track: Direction extends \"input\" ? MediaStreamTrack : ConsumerPlayer\n) => Return;\n"
  },
  {
    "path": "kebab/src/audio/mediasoup-client.ts",
    "content": "import { Device, Transport } from \"mediasoup-client/lib/types\";\nimport { ConnectFunction, ConsumerPlayer } from \"./interface\";\nimport { RoomPeer } from \"..\";\nimport { wrap } from \"./audioWrapper\";\n\nexport const makeConsumer = (transport: Transport) => async (data: RoomPeer) => ({\n  user: data.peerId,\n  consumer: await transport.consume({\n    ...data.consumerParameters,\n    appData: {\n      peerId: data.peerId,\n      producerId: data.consumerParameters.producerId,\n      mediaTag: \"cam-audio\"\n    }\n  })\n});\n\nexport const connect: ConnectFunction<(device: Device) => Promise<void>> = (\n  connection,\n  routerRtpCapabilities,\n  direction,\n  transportOptions,\n  track\n) => async (device) => {\n  if(!device.loaded) await device.load({ routerRtpCapabilities });\n\n  const wrapper = wrap(connection);\n  const simplerDirection = direction === \"output\" ? \"recv\" : \"send\";\n  const transport = direction === \"output\"\n    ? device.createRecvTransport(transportOptions)\n    : device.createSendTransport(transportOptions);\n\n  transport.on(\"connect\", async ({ dtlsParameters }, resolve, reject) => {\n    const result = await wrapper.mutation.connectTransport(transport.id, simplerDirection, dtlsParameters);\n\n    if(\"error\" in result) {\n      console.error(result.error); // eslint-disable-line no-console\n      reject();\n    } else {\n      resolve();\n    }\n  });\n\n  if(direction === \"input\") {\n    transport.on(\"produce\", async ({ kind, rtpParameters, appData }, resolve, reject) => {\n      const result = await wrapper.mutation.sendTrack(\n        transportOptions.id,\n        kind,\n        rtpParameters,\n        device.rtpCapabilities,\n        false,\n        appData,\n        simplerDirection as \"send\"\n      );\n\n      if(\"error\" in result) {\n        console.error(result.error); // eslint-disable-line no-console\n        reject();\n      } else {\n        resolve(result);\n      }\n    });\n\n    await transport.produce({\n      track: track as MediaStreamTrack,\n      appData: { mediaTag: \"cam-audio\" }\n    });\n  } else {\n    const { consumerParametersArr } = await wrapper.query.getConsumersParameters(device.rtpCapabilities);\n\n    const consumers = await Promise.all(consumerParametersArr.map(makeConsumer(transport)));\n    const unsubNps = wrapper.subscribe.newPeerSpeaker(async (peer) => {\n      consumers.push(await makeConsumer(transport)(peer));\n    });\n\n    const unsubCc = wrapper.subscribe.closeConsumer(({ producerId }) => {\n      const found = consumers.filter(it => it.consumer.producerId === producerId);\n\n      if(found[0]) consumers.splice(consumers.indexOf(found[0]), 1);\n    });\n\n    const unsubYlr = connection.addListener(\"you_left_room\", () => {\n      unsubYlr();\n      unsubNps();\n      unsubCc();\n    });\n\n    const giveTrack = track as ConsumerPlayer;\n\n    consumers.forEach(({ user, consumer }) => giveTrack(consumer.track, user));\n  }\n};\n"
  },
  {
    "path": "kebab/src/client/README.md",
    "content": "# Rewritten client\ni felt like the current system isn't declarative enough so i rewrote it just using typescript\n\n## How to add new stuff\nask me on discord\n"
  },
  {
    "path": "kebab/src/client/http/bot.ts",
    "content": "import { Endpoint } from \"./endpoint\";\n\nexport const auth: Endpoint<\n  { apiKey: string },\n  { username: string, accessToken: string, refreshToken: string }\n> = ({ apiKey }) => [\n  \"/bot/auth\",\n  {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: `{ \"apiKey\": \"${apiKey}\" }`\n  }\n];\n"
  },
  {
    "path": "kebab/src/client/http/dev.ts",
    "content": "import { Endpoint } from \"./endpoint\";\n\nexport const testInfo: Endpoint<\n  { username: string },\n  { accessToken: string, refreshToken: string }\n> = ({ username }) => [`/dev/test-info?username=${username}`, {}];\n"
  },
  {
    "path": "kebab/src/client/http/endpoint.ts",
    "content": "export type Endpoint<O, R> = (options: O) => [string, RequestInit]; // eslint-disable-line @typescript-eslint/no-unused-vars\n\nexport * as dev from \"./dev\";\nexport * as bot from \"./bot\";\n"
  },
  {
    "path": "kebab/src/client/http/index.ts",
    "content": "import { Endpoint } from \"./endpoint\";\nimport fetch from \"isomorphic-fetch\";\n\nexport const apiUrl = \"https://api.dogehouse.tv\";\n\nexport type HTTPRequester = <O, R>(endpoint: Endpoint<O, R>, options: O) => Promise<R>;\nexport const httpRequest: HTTPRequester = async (endpoint, options) => {\n  const [address, fetchOptions] = endpoint(options);\n\n  const response = await fetch(apiUrl + address, fetchOptions);\n\n  return await response.json() as any; // eslint-disable-line @typescript-eslint/no-explicit-any\n};\n\nexport * as httpEndpoint from \"./endpoint\";\n\n"
  },
  {
    "path": "kebab/src/client/index.ts",
    "content": "import { request, Requester } from \"./requester\";\nimport { subscribe, Subscriber } from \"./subscriber\";\nimport { raw } from \"../index\";\n\nexport type Client = raw.Connection & {\n  request: Requester,\n  subscribe: Subscriber\n};\n\nexport const createClient = (connection: raw.Connection): Client => ({\n  ...connection,\n  request: (name, data) => request(connection, name, data),\n  subscribe: (name, handler, options = {}) => subscribe(connection, name, handler, options),\n});\n\nexport * from \"./http\";\nexport * from \"./requester\";\nexport * from \"./subscriber\";\n"
  },
  {
    "path": "kebab/src/client/requester/auth.ts",
    "content": "import { EmptyObject } from \"../type-util\";\n\nexport default interface Requests {\n  \"request:but:you:dont:want:to:use:this\": EmptyObject,\n}\n"
  },
  {
    "path": "kebab/src/client/requester/chat.ts",
    "content": "import { MessageToken, UUID } from \"../..\";\n\nexport default interface Requests {\n  ban: {\n    request: { userId: UUID }\n  },\n  unban: {\n    request: { userId: UUID }\n  },\n  send_msg: {\n    request: {\n      tokens: MessageToken[],\n      whisperedTo: UUID[],\n      isWhisper?: boolean\n    }\n  },\n  delete: {\n    request: { messageId: UUID }\n  },\n}\n"
  },
  {
    "path": "kebab/src/client/requester/index.ts",
    "content": "import { raw } from \"../..\";\nimport UserRequests from \"./user\";\nimport RoomRequests from \"./room\";\nimport ChatRequests from \"./chat\";\nimport MiscRequests from \"./misc\";\nimport AuthRequests from \"./auth\";\nimport { DefaultValues, EmptyObject, GroupMap, NormalObjectKey } from \"../type-util\";\n\ntype DefaultRequest = {\n  request: EmptyObject,\n  reply: EmptyObject,\n  error: string\n};\n\ntype RequestMap = DefaultValues<\n  GroupMap<UserRequests, \":\", \"user\"> &\n  GroupMap<RoomRequests, \":\", \"room\"> &\n  GroupMap<ChatRequests, \":\", \"chat\"> &\n  GroupMap<MiscRequests, \":\", \"misc\"> &\n  GroupMap<AuthRequests, \":\", \"auth\">,\n\n  DefaultRequest\n>;\n\nexport type Requester = <R extends NormalObjectKey<keyof RequestMap>>(\n  name: R,\n  data: RequestMap[R][\"request\"]\n) => Promise<RequestMap[R][\"reply\"]>;\n\nexport type SimpleRequester = <R extends NormalObjectKey<keyof RequestMap>>(\n  connection: raw.Connection,\n  name: R,\n  data: RequestMap[R][\"request\"]\n) => Promise<RequestMap[R][\"reply\"]>;\n\nexport const request: SimpleRequester = async (connection, name, data) => {\n  const response = await connection.sendCall(name, data) as any; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  if(response.error) throw response.error;\n\n  return response;\n};\n"
  },
  {
    "path": "kebab/src/client/requester/misc.ts",
    "content": "import { Room, User } from \"../..\";\n\nexport default interface Requests {\n  search: {\n    request: { query: string },\n    reply: { items: Array<Room | User>, rooms: Room[], users: User[] }\n  },\n}\n"
  },
  {
    "path": "kebab/src/client/requester/room.ts",
    "content": "import { Room, RoomAuth, RoomRole, User, UUID, PaginatedReply, PaginatedRequest } from \"../..\";\n\nexport default interface Requests {\n  invite: {\n    request: { userId: UUID }\n  },\n  update: {\n    request: { name: string, description: string, isPrivate: boolean, autoSpeaker?: boolean },\n    reply: Room\n  },\n  get_invite_list: {\n    request: PaginatedRequest,\n    reply: { invites: User[] } & PaginatedReply\n  },\n  leave: void,\n  ban: {\n    request: { userId: UUID, shouldBadIp?: boolean }\n  },\n  set_role: {\n    request: { userId: UUID, role: RoomRole }\n  },\n  set_auth: {\n    request: { userId: UUID, level: RoomAuth }\n  },\n  join: {\n    request: { roomId: UUID },\n    reply: { name: string, description: string, isPrivate: boolean }\n  },\n  get_banned_users: {\n    request: PaginatedRequest,\n    reply: { users: User[] } & PaginatedReply\n  },\n  update_scheduled: {\n    request: { name: string, scheduledFor: string, description: string },\n    reply: unknown\n  },\n  delete_scheduled: {\n    request: { roomId: UUID }\n  },\n  create: {\n    request: {\n      name: string,\n      description: string,\n      isPrivate?: boolean,\n      userIdToInvite?: UUID[],\n      autoSpeaker?: boolean,\n      scheduledRoomId?: UUID\n    },\n    reply: Room\n  },\n  create_scheduled: {\n    request: {\n      name: string,\n      scheduledFor: string,\n      description?: string\n    },\n    reply: {\n      id: UUID,\n      name: string,\n      scheduledFor: string,\n      description: string\n    }\n  },\n  unban: {\n    request: { userId: UUID }\n  },\n  get_info: {\n    request: { roomId?: UUID },\n    reply: { name: string, description: string, isPrivate: boolean }\n  },\n  get_top: {\n    request: PaginatedRequest,\n    reply: { rooms: Room[] } & PaginatedReply\n  },\n  set_active_speaker: {\n    request: void,\n    reply: void\n  },\n  mute: {\n    request: { muted: boolean }\n  },\n  deafen: {\n    request: { deafened: boolean }\n  },\n  get_scheduled: {\n    request: { range?: \"all\" | \"upcoming\", userId: UUID, cursor: number },\n    reply: { rooms: Room[], nextCursor: number }\n  },\n}\n"
  },
  {
    "path": "kebab/src/client/requester/user.ts",
    "content": "import { Relationship, User, UUID, PaginatedReply, PaginatedRequest } from \"../..\";\n\nexport default interface Requests {\n  create_bot: {\n    request: {\n      username: string\n    },\n    reply: {\n      apiKey: string,\n      isUsernameTaken: boolean\n    }\n  },\n  ban: {\n    request: {\n      userId: UUID,\n      reason: string\n    },\n    reply: {\n      apiKey: string,\n      isUsernameTaken: boolean\n    }\n  },\n  block: {\n    request: {\n      userId: UUID\n    },\n    reply: {\n      blocked: UUID[]\n    }\n  },\n  unblock: {\n    request: {\n      userId: UUID\n    }\n  },\n  follow: {\n    request: {\n      userId: string\n    }\n  }\n  get_following: {\n    request: { username: string } & PaginatedRequest,\n    reply: { following: User } & PaginatedReply\n  }\n  get_followers: {\n    request: { username: string } & PaginatedRequest,\n    reply: { followers: User } & PaginatedReply\n  }\n  update: {\n    request: User,\n    reply: User\n  }\n  get_info: {\n    request: { userIdOrUsername: string },\n    reply: {\n      username: string,\n      displayName: string,\n      avatarUrl: string,\n      bannerUrl: string,\n      bio: string,\n      currentRoomId: UUID,\n      numFollowing: number,\n      numFollowers: number,\n      online: boolean,\n      lastOnline: string,\n      youAreFollowing?: boolean,\n      followsYou?: boolean,\n      iBlockedThem?: boolean\n    }\n  }\n  get_relationship: {\n    request: { userId: UUID },\n    reply: { relationship: Relationship[] }\n  },\n  unfollow: {\n    request: { userId: UUID }\n  },\n}\n"
  },
  {
    "path": "kebab/src/client/subscriber/index.ts",
    "content": "import { raw } from \"../..\";\nimport LegacyRequests from \"./legacy\";\nimport { NormalObjectKey } from \"../type-util\";\n\ntype EventMap = LegacyRequests;\n\nexport type EventHandler<D extends EventMap[keyof EventMap]> = (data: D) => void;\nexport type Unsubscriber = () => void;\n\nexport type Subscriber = <E extends NormalObjectKey<keyof EventMap>>(\n  name: E,\n  handler: EventHandler<EventMap[E]>,\n  options?: { once?: boolean }\n) => Unsubscriber;\n\nexport type SimpleSubscriber = <E extends NormalObjectKey<keyof EventMap>>(\n  connection: raw.Connection,\n  name: E,\n  handler: EventHandler<EventMap[E]>,\n  options?: { once?: boolean }\n) => Unsubscriber;\n\nexport const subscribe: SimpleSubscriber = (connection, name, handler, { once = false } = {}) => {\n  const unsubscribe = connection.addListener(\n    name,\n    once\n      ? data => {\n        unsubscribe();\n        handler(data as any); // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n      : handler as any // eslint-disable-line @typescript-eslint/no-explicit-any\n  );\n\n  return unsubscribe;\n};\n"
  },
  {
    "path": "kebab/src/client/subscriber/legacy.ts",
    "content": "import { BooleanMap, Message, RoomDetails, User, UUID } from \"../../entities\";\n\nexport default interface Events {\n  new_chat_msg: { userId: UUID, msg: Message },\n  new_room_details: RoomDetails,\n  new_user_join_room: { user: User },\n  user_left_room: { userId: UUID },\n  invitation_to_room: {\n    type: \"invite\";\n    username: string;\n    displayName: string;\n    avatarUrl: string;\n    bannerUrl: string;\n    roomName: string;\n    roomId: UUID;\n  },\n  hand_raised: { userId: UUID },\n  speaker_added: {\n    userId: UUID;\n    muteMap: BooleanMap;\n    deafMap: BooleanMap;\n  },\n  speaker_removed: {\n    userId: UUID;\n    muteMap: BooleanMap;\n    deafMap: BooleanMap;\n  }\n}\n"
  },
  {
    "path": "kebab/src/client/type-util.ts",
    "content": "export type NormalObjectKey<T> = Exclude<T, symbol | number>;\n\nexport type DefaultValues<T, D> = {\n  [K in keyof T]: D & T[K];\n};\n\nexport type GroupMap<Group, Separator extends string, GroupName extends string> = {\n  [RequestName in keyof Group as `${GroupName}${Separator}${NormalObjectKey<RequestName>}`]: Group[RequestName];\n};\n\nexport type EmptyObject = Record<string | symbol | number, unknown>;\n"
  },
  {
    "path": "kebab/src/entities.ts",
    "content": "import { ConsumerOptions } from \"mediasoup-client/lib/types\";\n\nexport type UUID = string;\n\nexport type RoomPeer = {\n  peerId: UUID;\n  consumerParameters: ConsumerOptions;\n};\n\nexport type UserPreview = {\n  id: UUID;\n  displayName: string;\n  numFollowers: number;\n  avatarUrl: string | null;\n};\n\nexport type ChatMode = \"default\" | \"disabled\" | \"follower_only\";\n\nexport type RoomDetails = {\n  name: string;\n  chatThrottle: number;\n  isPrivate: boolean;\n  description: string;\n};\n\nexport type Room = RoomDetails & {\n  id: string;\n  numPeopleInside: number;\n  voiceServerId: string;\n  creatorId: string;\n  peoplePreviewList: Array<UserPreview>;\n  autoSpeaker: boolean;\n  inserted_at: string;\n  chatMode: ChatMode;\n};\n\nexport interface ScheduledRoom {\n  roomId: UUID | null;\n  description: string;\n  scheduledFor: string;\n  numAttending: number;\n  name: string;\n  id: UUID;\n  creatorId: UUID;\n  creator: User;\n}\n\nexport type User = {\n  youAreFollowing?: boolean;\n  username: string;\n  online: boolean;\n  numFollowing: number;\n  numFollowers: number;\n  lastOnline: string;\n  id: UUID;\n  followsYou?: boolean;\n  botOwnerId?: string | null;\n  contributions: number;\n  staff: boolean;\n  displayName: string;\n  currentRoomId?: UUID | null;\n  currentRoom: Room;\n  bio: string | null;\n  avatarUrl: string;\n  bannerUrl: string | null;\n  whisperPrivacySetting: \"on\" | \"off\";\n};\n\nexport type MessageToken<T extends string = string, V = unknown> = {\n  t: T;\n  v: V;\n};\n\nexport type TextToken = MessageToken<\"text\", string>;\nexport type MentionToken = MessageToken<\"mention\", string>;\nexport type LinkToken = MessageToken<\"link\", string>;\nexport type EmoteToken = MessageToken<\"emote\", string>;\nexport type CodeBlockToken = MessageToken<\"block\", string>;\nexport type EmojiToken = MessageToken<\"emoji\", string>;\n\nexport type Message = {\n  id: UUID;\n  userId: UUID;\n  avatarUrl: UUID;\n  color: string;\n  displayName: string;\n  tokens: MessageToken[];\n  username: string;\n  deleted?: boolean;\n  deleterId?: UUID;\n  sentAt: string;\n  isWhisper?: boolean;\n};\n\nexport type BaseUser = {\n  username: string;\n  online: boolean;\n  lastOnline: string;\n  id: string;\n  bio: string;\n  displayName: string;\n  avatarUrl: string;\n  bannerUrl: string;\n  numFollowing: number;\n  numFollowers: number;\n  currentRoom?: Room;\n  botOwnerId?: string;\n  contributions: number;\n  staff: boolean;\n};\n\nexport type RoomPermissions = {\n  askedToSpeak: boolean;\n  isSpeaker: boolean;\n  isMod: boolean;\n};\n\nexport type UserWithFollowInfo = BaseUser & {\n  followsYou?: boolean;\n  youAreFollowing?: boolean;\n  iBlockedThem?: boolean;\n};\n\nexport type RoomUser = {\n  roomPermissions?: RoomPermissions | null;\n} & UserWithFollowInfo;\n\nexport type CurrentRoom = Room & {\n  users: RoomUser[];\n  muteMap: BooleanMap;\n  deafMap: BooleanMap;\n  activeSpeakerMap: BooleanMap;\n  autoSpeaker: boolean;\n};\n\nexport type BooleanMap = Record<UUID, boolean>;\n\nexport enum Relationship {\n  self = 0,\n  following = 1,\n  follower = 2,\n  mutual = 3,\n  none = 7\n}\n\nexport enum RoomRole {\n  speaker = 8,\n  raised_hand = 16,\n  listener = 32\n}\n\nexport enum RoomAuth {\n  owner = 8,\n  mod = 16,\n  user = 32\n}\n"
  },
  {
    "path": "kebab/src/http/bot.ts",
    "content": "import { request } from \"./raw\";\n\nexport type AuthResponse = {\n  username: string;\n  accessToken: string;\n  refreshToken: string;\n};\n\n/**\n * Login to the api using you bot's apiKey\n * @param {string} apiKey You bot's apiKey\n * @returns {AuthResponse} Bot's username, accessToken and refreshToken\n */\nexport const auth = async (apiKey: string) =>\n  (await request(\"POST\", \"/bot/auth\", { apiKey })) as AuthResponse;\n"
  },
  {
    "path": "kebab/src/http/index.ts",
    "content": "export * from \"./raw\";\nexport * from \"./wrapper\";\nexport * as bot from \"./bot\";\n"
  },
  {
    "path": "kebab/src/http/raw.ts",
    "content": "import fetch from \"isomorphic-unfetch\";\n\nconst BASE_URL = \"https://api.dogehouse.tv\";\n\ninterface Options {\n  baseUrl?: string;\n}\n\ntype Request = (\n  method: string,\n  endpoint: string,\n  body?: unknown,\n  opts?: Options\n) => Promise<unknown>;\n\nexport type Http = {\n  request: Request;\n};\n\nexport const create = (baseOpts: Options): Http => {\n  return {\n    request: async (\n      method: string,\n      endpoint: string,\n      body?: unknown,\n      opts: Options = {}\n    ) => {\n      const { baseUrl = BASE_URL } = { ...baseOpts, ...opts };\n\n      return await fetch(`${baseUrl}${endpoint}`, {\n        method,\n        headers: { \"Content-Type\": \"application/json\" },\n        body: body ? JSON.stringify(body) : undefined,\n      }).then((res) => res.json());\n    },\n  };\n};\n\n// for backward compat, you can kill this if you don't want it anymore\nexport const request: Request = (...params) => create({}).request(...params);\n"
  },
  {
    "path": "kebab/src/http/wrapper.ts",
    "content": "import { Http } from \"./raw\";\n\nexport type AuthResponse = {\n  username: string;\n  accessToken: string;\n  refreshToken: string;\n};\n\nexport const wrap = (http: Http) => {\n  return {\n    auth: (apiKey: string) =>\n      http.request(\"POST\", \"/bot/auth\", { apiKey }) as Promise<AuthResponse>,\n    testUser: (username: string) =>\n      http.request(\"GET\", `/dev/test-info?username=${username}`) as Promise<{\n        accessToken: string;\n        refreshToken: string;\n      }>,\n  };\n};\n"
  },
  {
    "path": "kebab/src/index.ts",
    "content": "export * from \"./websocket\";\nexport { wrap as audioWrap } from \"./audio/audioWrapper\";\nexport * as http from \"./http\";\n\nexport * from \"./entities\";\nexport * from \"./util\";\nexport * from \"./pagination\";\nexport * from \"./client\";\n"
  },
  {
    "path": "kebab/src/pagination.ts",
    "content": "export type PaginatedRequest<C extends number | string = number> = {\n  cursor: C,\n  limit: number\n};\n\nexport type PaginatedReply<C extends number | string = number> = {\n  nextCursor: C,\n  initial: boolean\n};\n"
  },
  {
    "path": "kebab/src/util/ast.test.ts",
    "content": "import { tokensToString, stringToToken } from \"./ast\";\n\ntest(\"to tokens\", () => {\n  expect(stringToToken(\"abcd\")).toEqual([{ t: \"text\", v: \"abcd\" }]);\n});\n\ntest(\"to string\", () => {\n  expect(tokensToString([{ t: \"text\", v: \"abcd\" }])).toEqual(\"abcd\");\n  expect(tokensToString([{ t: \"nonexistent-type\", v: \"abcd\" }])).toEqual(\"\");\n});\n"
  },
  {
    "path": "kebab/src/util/ast.ts",
    "content": "import { MessageToken, TextToken } from \"..\";\n\n/**\n *\n * @param {string} string The string you want to convert to TextTokens\n * @returns {TextToken} TextToken[]\n */\nexport const stringToToken = (string: string): [TextToken] => [{ t: \"text\", v: string }];\n\n/**\n * @param {MessageToken[]} tokens MessageTokens to be converted to string\n * @returns {string} string\n */\nexport const tokensToString = (tokens: MessageToken[]): string => tokens\n  .map(it => {\n    switch (it.t) {\n      case \"text\": return it.v;\n      case \"mention\": return `@${it.v}`;\n      case \"link\": return it.v;\n      case \"emote\": return `:${it.v}:`;\n      case \"block\": return `\\`${it.v}\\``;\n      case \"emoji\": return it.v;\n      default: return \"\";\n    }\n  })\n  .join(\" \");\n"
  },
  {
    "path": "kebab/src/util/index.ts",
    "content": "export * from \"./ast\";\n"
  },
  {
    "path": "kebab/src/websocket/index.ts",
    "content": "export * as raw from \"./raw\";\nexport * from \"./wrapper\";\nexport * from \"./responses\";\n"
  },
  {
    "path": "kebab/src/websocket/raw.ts",
    "content": "import WebSocket from \"isomorphic-ws\";\nimport ReconnectingWebSocket from \"reconnecting-websocket\";\nimport { v4 as generateUuid } from \"uuid\";\nimport { User, UUID } from \"..\";\n\nconst heartbeatInterval = 8000;\nconst apiUrl = \"wss://api.dogehouse.tv/socket\";\n// const apiUrl = \"ws://localhost:4001/socket\";\nconst connectionTimeout = 15000;\n\nexport type Token = string;\n\n/**\n * @deprecated\n */\nexport type FetchID = UUID;\nexport type Ref = UUID;\nexport type Opcode = string;\nexport type Logger = (\n  direction: \"in\" | \"out\",\n  opcode: Opcode,\n  data?: unknown,\n  fetchId?: Ref,\n  raw?: string\n) => void;\nexport type ListenerHandler<Data = unknown> = (\n  data: Data,\n  fetchId?: Ref\n) => void;\nexport type Listener<Data = unknown> = {\n  opcode: Opcode;\n  handler: ListenerHandler<Data>;\n};\n\n/**\n * A reference to the websocket connection, can be created using `connect()`\n */\nexport type Connection = {\n  close: () => void;\n\n  /**\n   * @deprecated\n   */\n  once: <Data = unknown>(\n    opcode: Opcode,\n    handler: ListenerHandler<Data>\n  ) => void;\n  addListener: <Data = unknown>(\n    opcode: Opcode,\n    handler: ListenerHandler<Data>\n  ) => () => void;\n  user: User;\n  initialCurrentRoomId?: string;\n\n  /**\n   * @deprecated\n   */\n  send: (opcode: Opcode, data: unknown, fetchId?: FetchID) => void;\n  sendCast: (opcode: Opcode, data: unknown, ref?: Ref) => void;\n\n  /**\n   * @deprecated\n   */\n  fetch: (\n    opcode: Opcode,\n    data: unknown,\n    doneOpcode?: Opcode\n  ) => Promise<unknown>;\n  sendCall: (\n    opcode: Opcode,\n    data: unknown,\n    doneOpcode?: Opcode\n  ) => Promise<unknown>;\n};\n\n// probably want to remove token/refreshToken\n// better to use getAuthOptions\n// when ws tries to reconnect it should use current tokens not the ones it initializes with\n/**\n * Creates a Connection object\n * @param {Token} token Your dogehouse token\n * @param {Token} refreshToken Your dogehouse refresh token\n * @returns {Promise<Connection>} Connection object\n */\nexport const connect = (\n  token: Token,\n  refreshToken: Token,\n  {\n    logger = () => {},\n    onConnectionTaken = () => {},\n    onClearTokens = () => {},\n    url = apiUrl,\n    fetchTimeout,\n    getAuthOptions,\n    waitToReconnect,\n  }: {\n    logger?: Logger;\n    onConnectionTaken?: () => void;\n    onClearTokens?: () => void;\n    url?: string;\n    fetchTimeout?: number;\n    waitToReconnect?: boolean;\n    getAuthOptions?: () => Partial<{\n      reconnectToVoice: boolean;\n      currentRoomId: string | null;\n      muted: boolean;\n      deafened: boolean;\n      token: Token;\n      refreshToken: Token;\n    }>;\n  }\n): Promise<Connection> =>\n  new Promise((resolve, reject) => {\n    const socket = new ReconnectingWebSocket(url, [], {\n      connectionTimeout,\n      WebSocket,\n    });\n    const api2Send = (opcode: Opcode, data: unknown, ref?: Ref) => {\n      // tmp fix\n      // this is to avoid ws events queuing up while socket is closed\n      // then it reconnects and fires before auth goes off\n      // and you get logged out\n      if (socket.readyState !== socket.OPEN) return;\n\n      const raw = `{\"v\":\"0.2.0\", \"op\":\"${opcode}\",\"p\":${JSON.stringify(data)}${\n        ref ? `,\"ref\":\"${ref}\"` : \"\"\n      }}`;\n\n      socket.send(raw);\n      logger(\"out\", opcode, data, ref, raw);\n    };\n    const apiSend = (opcode: Opcode, data: unknown, fetchId?: FetchID) => {\n      // tmp fix\n      // this is to avoid ws events queuing up while socket is closed\n      // then it reconnects and fires before auth goes off\n      // and you get logged out\n      if (socket.readyState !== socket.OPEN) {\n        return;\n      }\n      const raw = `{\"op\":\"${opcode}\",\"d\":${JSON.stringify(data)}${\n        fetchId ? `,\"fetchId\":\"${fetchId}\"` : \"\"\n      }}`;\n\n      socket.send(raw);\n      logger(\"out\", opcode, data, fetchId, raw);\n    };\n\n    const listeners: Listener[] = [];\n\n    // close & message listener needs to be outside of open\n    // this prevents multiple listeners from being created on reconnect\n    socket.addEventListener(\"close\", (error) => {\n      // I want this here\n      // eslint-disable-next-line no-console\n      console.log(error);\n      if (error.code === 4001) {\n        socket.close();\n        onClearTokens();\n      } else if (error.code === 4003) {\n        socket.close();\n        onConnectionTaken();\n      } else if (error.code === 4004) {\n        socket.close();\n        onClearTokens();\n      }\n\n      if (!waitToReconnect) reject(error);\n    });\n\n    socket.addEventListener(\"message\", (e) => {\n      if (e.data === `\"pong\"` || e.data === `pong`) {\n        logger(\"in\", \"pong\");\n\n        return;\n      }\n\n      const message = JSON.parse(e.data);\n\n      logger(\"in\", message.op, message.d, message.fetchId, e.data);\n\n      if (message.op === \"auth-good\") {\n        const connection: Connection = {\n          close: () => socket.close(),\n          once: (opcode, handler) => {\n            const listener = { opcode, handler } as Listener<unknown>;\n\n            listener.handler = (...params) => {\n              handler(...(params as Parameters<typeof handler>));\n              listeners.splice(listeners.indexOf(listener), 1);\n            };\n\n            listeners.push(listener);\n          },\n          addListener: (opcode, handler) => {\n            const listener = { opcode, handler } as Listener<unknown>;\n\n            listeners.push(listener);\n\n            return () => listeners.splice(listeners.indexOf(listener), 1);\n          },\n          user: message.d.user,\n          send: apiSend,\n          sendCast: api2Send,\n          sendCall: (\n            opcode: Opcode,\n            parameters: unknown,\n            doneOpcode?: Opcode\n          ) =>\n            new Promise((resolveCall, rejectFetch) => {\n              // tmp fix\n              // this is to avoid ws events queuing up while socket is closed\n              // then it reconnects and fires before auth goes off\n              // and you get logged out\n              if (socket.readyState !== socket.OPEN) {\n                rejectFetch(new Error(\"websocket not connected\"));\n\n                return;\n              }\n              const ref: FetchID | false = !doneOpcode && generateUuid();\n              let timeoutId: NodeJS.Timeout | null = null;\n              const unsubscribe = connection.addListener(\n                doneOpcode ?? opcode + \":reply\",\n                (data, arrivedId) => {\n                  if (!doneOpcode && arrivedId !== ref) return;\n\n                  if (timeoutId) clearTimeout(timeoutId);\n\n                  unsubscribe();\n                  resolveCall(data);\n                }\n              );\n\n              if (fetchTimeout) {\n                timeoutId = setTimeout(() => {\n                  unsubscribe();\n                  rejectFetch(new Error(\"timed out\"));\n                }, fetchTimeout);\n              }\n\n              api2Send(opcode, parameters, ref || undefined);\n            }),\n          fetch: (opcode: Opcode, parameters: unknown, doneOpcode?: Opcode) =>\n            new Promise((resolveFetch, rejectFetch) => {\n              // tmp fix\n              // this is to avoid ws events queuing up while socket is closed\n              // then it reconnects and fires before auth goes off\n              // and you get logged out\n              if (socket.readyState !== socket.OPEN) {\n                rejectFetch(new Error(\"websocket not connected\"));\n\n                return;\n              }\n              const fetchId: FetchID | false = !doneOpcode && generateUuid();\n              let timeoutId: NodeJS.Timeout | null = null;\n              const unsubscribe = connection.addListener(\n                doneOpcode ?? \"fetch_done\",\n                (data, arrivedId) => {\n                  if (!doneOpcode && arrivedId !== fetchId) return;\n\n                  if (timeoutId) clearTimeout(timeoutId);\n\n                  unsubscribe();\n                  resolveFetch(data);\n                }\n              );\n\n              if (fetchTimeout) {\n                timeoutId = setTimeout(() => {\n                  unsubscribe();\n                  rejectFetch(new Error(\"timed out\"));\n                }, fetchTimeout);\n              }\n\n              apiSend(opcode, parameters, fetchId || undefined);\n            }),\n        };\n\n        resolve(connection);\n      } else {\n        listeners\n          .filter(({ opcode }) => opcode === message.op)\n          .forEach((it) =>\n            it.handler(message.d || message.p, message.fetchId || message.ref)\n          );\n      }\n    });\n\n    socket.addEventListener(\"open\", () => {\n      const id = setInterval(() => {\n        if (socket.readyState === socket.CLOSED) {\n          clearInterval(id);\n        } else {\n          socket.send(\"ping\");\n          logger(\"out\", \"ping\");\n        }\n      }, heartbeatInterval);\n\n      apiSend(\"auth\", {\n        accessToken: token,\n        refreshToken,\n        reconnectToVoice: false,\n        currentRoomId: null,\n        muted: false,\n        deafened: false,\n        ...getAuthOptions?.(),\n      });\n    });\n  });\n"
  },
  {
    "path": "kebab/src/websocket/responses.ts",
    "content": "import { Room, RoomUser, ScheduledRoom, User, UUID } from \"../entities\";\n\nexport type GetTopPublicRoomsResponse = {\n  rooms: Room[];\n  nextCursor: number | null;\n};\n\nexport type GetScheduledRoomsResponse = {\n  nextCursor: string | null;\n  rooms: ScheduledRoom[];\n};\n\nexport type JoinRoomAndGetInfoResponse = {\n  room: Room;\n  users: RoomUser[];\n  muteMap: Record<string, boolean>;\n  deafMap: Record<string, boolean>;\n  roomId: string;\n  activeSpeakerMap: Record<string, boolean>;\n};\n\nexport type GetRoomUsersResponse = {\n  users: User[];\n  roomId: UUID;\n  raiseHandMap: Record<string, boolean>;\n  muteMap: Record<string, boolean>;\n  deafMap: Record<string, boolean>;\n  autoSpeaker: boolean;\n  activeSpeakerMap: Record<string, boolean>;\n};\n\nexport type NewRoomDetailsResponse = {\n  roomId: UUID;\n  name: string;\n  chatThrottle: number;\n  isPrivate: boolean;\n  description: string;\n};\n\nexport type InvitationToRoomResponse = {\n  type: \"invite\";\n  username: string;\n  displayName: string;\n  avatarUrl: string;\n  bannerUrl: string;\n  roomName: string;\n  roomId: UUID;\n};\n\nexport type CreateBotResponse = {\n  apiKey: string | null;\n  isUsernameTaken: boolean | null;\n  error: string | null;\n};\n"
  },
  {
    "path": "kebab/src/websocket/wrapper.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck because internet is unpredictable\n\nimport {\n  Message,\n  MessageToken,\n  BooleanMap,\n  Room,\n  ScheduledRoom,\n  User,\n  UserWithFollowInfo,\n  UUID,\n} from \"..\";\nimport { ChatMode } from \"../entities\";\nimport { Connection } from \"./raw\";\nimport {\n  GetScheduledRoomsResponse,\n  GetTopPublicRoomsResponse,\n  JoinRoomAndGetInfoResponse,\n  GetRoomUsersResponse,\n  NewRoomDetailsResponse,\n  InvitationToRoomResponse,\n  CreateBotResponse,\n} from \"./responses\";\n\n/**\n * Allows you to handle custom logic on websocket events\n */\ntype Handler<Data> = (data: Data) => void;\n\n/**\n * A wrapper object created using `wrap()` that can be used to make websocket calls using functions\n */\nexport type Wrapper = ReturnType<typeof wrap>;\n\n/**\n * Creates a wrapper object that allows you to make websocket calls using functions\n * @param {Connection} connection reference to the websocket connection\n * @returns  {connection} Wrapper object\n */\nexport const wrap = (connection: Connection) => ({\n  connection,\n\n  /**\n   * Allows you to subscribe to various pre-defined websocket events\n   */\n  subscribe: {\n    newChatMsg: (handler: Handler<{ userId: UUID; msg: Message }>) =>\n      connection.addListener(\"new_chat_msg\", handler),\n    newRoomDetails: (handler: Handler<NewRoomDetailsResponse>) =>\n      connection.addListener(\"new_room_details\", handler),\n    userJoinRoom: (handler: Handler<{ user: User }>) =>\n      connection.addListener(\"new_user_join_room\", handler),\n    userLeaveRoom: (handler: Handler<{ userId: UUID; roomId: UUID }>) =>\n      connection.addListener(\"user_left_room\", handler),\n    invitationToRoom: (handler: Handler<InvitationToRoomResponse>) =>\n      connection.addListener(\"invitation_to_room\", handler),\n    handRaised: (handler: Handler<{ userId: UUID }>) =>\n      connection.addListener(\"hand_raised\", handler),\n    speakerAdded: (\n      handler: Handler<{\n        userId: UUID;\n        muteMap: BooleanMap;\n        deafMap: BooleanMap;\n      }>\n    ) => connection.addListener(\"speaker_added\", handler),\n    speakerRemoved: (\n      handler: Handler<{\n        userId: UUID;\n        muteMap: BooleanMap;\n        deafMap: BooleanMap;\n      }>\n    ) => connection.addListener(\"speaker_removed\", handler),\n  },\n\n  /**\n   * Allows you to call functions that return information about the ws state\n   */\n\n  query: {\n    search: (\n      query: string\n    ): Promise<{\n      items: Array<User | Room>;\n      rooms: Room[];\n      users: User[];\n    }> => connection.fetch(\"search\", { query }),\n    getMyScheduledRoomsAboutToStart: (\n      roomId: string\n    ): Promise<{ scheduledRooms: ScheduledRoom[] }> =>\n      connection.fetch(\"get_my_scheduled_rooms_about_to_start\", { roomId }),\n    joinRoomAndGetInfo: (\n      roomId: string\n    ): Promise<JoinRoomAndGetInfoResponse | { error: string }> =>\n      connection.fetch(\"join_room_and_get_info\", { roomId }),\n    getInviteList: (\n      cursor = 0\n    ): Promise<{\n      users: User[];\n      nextCursor: number | null;\n    }> => connection.fetch(\"get_invite_list\", { cursor }),\n    getFollowList: (\n      username: string,\n      isFollowing: boolean,\n      cursor = 0\n    ): Promise<{\n      users: UserWithFollowInfo[];\n      nextCursor: number | null;\n    }> =>\n      connection.fetch(\"get_follow_list\", { username, isFollowing, cursor }),\n    getBlockedFromRoomUsers: (\n      cursor = 0\n    ): Promise<{\n      users: User[];\n      nextCursor: number | null;\n    }> => connection.fetch(\"get_blocked_from_room_users\", { offset: cursor }),\n    getMyFollowing: (\n      cursor = 0\n    ): Promise<{\n      users: UserWithFollowInfo[];\n      nextCursor: number | null;\n    }> => connection.fetch(\"get_my_following\", { cursor }),\n    getTopPublicRooms: (cursor = 0): Promise<GetTopPublicRoomsResponse> =>\n      connection.fetch(\"get_top_public_rooms\", { cursor }),\n    getUserProfile: (\n      idOrUsername: string\n    ): Promise<UserWithFollowInfo | null | { error: string }> =>\n      connection.fetch(\"get_user_profile\", { userId: idOrUsername }),\n    getScheduledRooms: (\n      cursor = \"\",\n      range: \"all\" | \"upcoming\" = \"all\",\n      userId: string | undefined,\n    ): Promise<GetScheduledRoomsResponse> =>\n      connection.sendCall(\"room:get_scheduled\", {\n        cursor,\n        range,\n        userId,\n      }),\n    getRoomUsers: (): Promise<GetRoomUsersResponse> =>\n      connection.fetch(\n        \"get_current_room_users\",\n        {},\n        \"get_current_room_users_done\"\n      ),\n  },\n\n  /**\n   * Allows you to call functions that mutate the ws state\n   */\n  mutation: {\n    userUpdate: (data: Partial<User>): Promise<void> =>\n      connection.sendCall(\"user:update\", data),\n    userBlock: (userId: string): Promise<void> =>\n      connection.sendCall(\"user:block\", { userId }),\n    userUnblock: (userId: string): Promise<void> =>\n      connection.sendCall(\"user:unblock\", { userId }),\n    roomUpdate: (data: {\n      name?: string;\n      privacy?: string;\n      chatThrottle?: number;\n      description?: string;\n      autoSpeaker?: boolean;\n      chatMode?: ChatMode;\n    }): Promise<void> => connection.sendCall(\"room:update\", data),\n    roomBan: (userId: string, shouldBanIp?: boolean): Promise<void> =>\n      connection.sendCast(\"room:ban\", { userId, shouldBanIp }),\n    setDeaf: (isDeafened: boolean): Promise<Record<string, never>> =>\n      connection.sendCall(\"room:deafen\", { deafened: isDeafened }),\n    userCreateBot: (username: string): Promise<CreateBotResponse> =>\n      connection.sendCall(`user:create_bot`, { username }),\n    userAdminUpdate: (\n      id: UUID,\n      user: {\n        staff?: boolean;\n        contributions?: number;\n      }\n    ) =>\n      connection.sendCall(`user:admin_update`, {\n        id,\n        user,\n      }),\n    ban: (username: string, reason: string) =>\n      connection.send(`ban`, { username, reason }),\n    deleteScheduledRoom: (id: string): Promise =>\n      connection.fetch(`delete_scheduled_room`, { id }),\n    createRoomFromScheduledRoom: (data: {\n      id: string;\n      name: string;\n      description: string;\n    }): Promise<{ room: Room }> =>\n      connection.fetch(`create_room_from_scheduled_room`, data),\n    createScheduledRoom: (data: {\n      name: string;\n      description: string;\n      scheduledFor: string;\n    }): Promise<{ error: string } | ScheduledRoom> =>\n      connection.fetch(`schedule_room`, data),\n    editScheduledRoom: (\n      id: string,\n      data: {\n        name: string;\n        description: string;\n        scheduledFor: string;\n      }\n    ): Promise<{ error: string } | ScheduledRoom> =>\n      connection.fetch(`edit_scheduled_room`, { id, data }),\n    askToSpeak: () => connection.send(`ask_to_speak`, {}),\n    inviteToRoom: (userId: string) =>\n      connection.send(`invite_to_room`, { userId }),\n    speakingChange: (value: boolean) =>\n      connection.send(`speaking_change`, { value }),\n    unbanFromRoom: (userId: string): Promise<void> =>\n      connection.fetch(\"unban_from_room\", { userId }),\n    follow: (userId: string, value: boolean): Promise<void> =>\n      connection.fetch(\"follow\", { userId, value }),\n    sendRoomChatMsg: (\n      ast: MessageToken[],\n      whisperedTo: string[] = []\n    ): Promise<void> =>\n      connection.send(\"send_room_chat_msg\", { tokens: ast, whisperedTo }),\n    changeModStatus: (userId: string, value: boolean): Promise<void> =>\n      connection.send(\"change_mod_status\", { userId, value }),\n    changeRoomCreator: (userId: string): Promise<void> =>\n      connection.send(\"change_room_creator\", { userId }),\n    addSpeaker: (userId: string): Promise<void> =>\n      connection.send(\"add_speaker\", { userId }),\n    deleteRoomChatMessage: (userId: string, messageId: string): Promise<void> =>\n      connection.send(\"delete_room_chat_message\", { userId, messageId }),\n    unbanFromRoomChat: (userId: string): Promise<void> =>\n      connection.send(\"unban_from_room_chat\", { userId }),\n    banFromRoomChat: (userId: string): Promise<void> =>\n      connection.send(\"ban_from_room_chat\", { userId }),\n    setListener: (userId: string): Promise<void> =>\n      connection.send(\"set_listener\", { userId }),\n    setMute: (isMuted: boolean): Promise<Record<string, never>> =>\n      connection.fetch(\"mute\", { value: isMuted }),\n    leaveRoom: (): Promise<{ roomId: UUID }> =>\n      connection.fetch(\"leave_room\", {}, \"you_left_room\"),\n    createRoom: (data: {\n      name: string;\n      privacy: string;\n      description: string;\n    }): Promise<{ error: string } | { room: Room }> =>\n      connection.fetch(\"create_room\", data),\n    editProfile: (data: {\n      displayName: string;\n      username: string;\n      bio: string;\n      avatarUrl: string;\n      bannerUrl?: string;\n    }): Promise<{ isUsernameTaken: boolean }> =>\n      connection.fetch(\"edit_profile\", { data }),\n    editRoom: (data: {\n      name: string;\n      privacy: string;\n      description: string;\n    }): Promise<{ error: string } | { room: Room }> =>\n      connection.fetch(\"edit_room\", data),\n  },\n});\n"
  },
  {
    "path": "kebab/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"skipLibCheck\": true,\n    \"outDir\": \"./lib\",\n    \"strict\": true,\n    \"lib\": [\"dom\", \"ES6\"],\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"src/**/*.test.ts\"]\n}\n"
  },
  {
    "path": "kibbeh/.babelrc",
    "content": "{\n  \"presets\": [\"next/babel\"]\n}\n"
  },
  {
    "path": "kibbeh/.eslintignore",
    "content": "node_modules/\n\n*.spec.tsx\n*.spec.tsx.snap\nsrc/generated/\n"
  },
  {
    "path": "kibbeh/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:react-hooks/recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:cypress/recommended\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 12,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"react\", \"@typescript-eslint/eslint-plugin\", \"react-hooks\"],\n  \"settings\": {\n    \"react\": {\n      \"pragma\": \"React\",\n      \"version\": \"detect\"\n    },\n    \"import/resolver\": \"parcel2\",\n    \"html/xml-extensions\": [\".html\"],\n    \"html/indent\": \"+2\"\n  },\n  \"rules\": {\n    \"@typescript-eslint/no-unused-vars\": \"off\",\n    \"@typescript-eslint/no-explicit-any\": \"off\",\n    \"@typescript-eslint/no-var-requires\": \"off\",\n    \"@typescript-eslint/no-non-null-assertion\": \"off\",\n    \"@typescript-eslint/no-empty-interface\": \"off\",\n    \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n    \"@typescript-eslint/member-delimiter-style\": \"off\",\n    \"no-empty\": \"off\",\n    \"no-empty-pattern\": \"off\",\n    \"react/prop-types\": \"off\",\n    \"accessor-pairs\": \"error\",\n    \"array-bracket-newline\": \"off\",\n    \"array-bracket-spacing\": \"error\",\n    \"array-callback-return\": \"off\",\n    \"array-element-newline\": [\"error\", \"consistent\"],\n    \"arrow-body-style\": \"off\",\n    \"arrow-parens\": \"off\",\n    \"arrow-spacing\": \"error\",\n    \"block-scoped-var\": \"error\",\n    \"block-spacing\": \"error\",\n    \"brace-style\": \"error\",\n    \"camelcase\": \"off\",\n    \"comma-dangle\": \"off\",\n    \"comma-spacing\": \"error\",\n    \"comma-style\": \"error\",\n    \"complexity\": \"off\",\n    \"computed-property-spacing\": \"error\",\n    \"consistent-this\": \"error\",\n    \"default-case-last\": \"error\",\n    \"default-param-last\": \"error\",\n    \"dot-location\": [\"error\", \"property\"],\n    \"dot-notation\": \"error\",\n    \"eol-last\": \"error\",\n    \"eqeqeq\": \"error\",\n    \"func-call-spacing\": \"off\",\n    \"func-name-matching\": \"error\",\n    \"func-names\": \"error\",\n    \"func-style\": [\n      \"error\",\n      \"declaration\",\n      {\n        \"allowArrowFunctions\": true\n      }\n    ],\n    \"function-call-argument-newline\": [\"error\", \"consistent\"],\n    \"function-paren-newline\": \"off\",\n    \"generator-star-spacing\": \"error\",\n    \"global-require\": \"error\",\n    \"grouped-accessor-pairs\": \"error\",\n    \"guard-for-in\": \"error\",\n    \"handle-callback-err\": \"error\",\n    \"id-blacklist\": \"error\",\n    \"id-denylist\": \"error\",\n    \"id-match\": \"error\",\n    \"indent\": [\n      \"off\",\n      2,\n      {\n        \"offsetTernaryExpressions\": true\n      }\n    ],\n    \"init-declarations\": \"error\",\n    \"jsx-quotes\": \"error\",\n    \"key-spacing\": [\"error\"],\n    \"keyword-spacing\": \"off\",\n    \"line-comment-position\": \"off\",\n    \"linebreak-style\": \"error\",\n    \"lines-around-comment\": \"off\",\n    \"lines-around-directive\": \"error\",\n    \"lines-between-class-members\": \"error\",\n    \"max-classes-per-file\": \"error\",\n    \"max-depth\": \"error\",\n    \"max-len\": \"off\",\n    \"max-lines\": \"off\",\n    \"max-lines-per-function\": \"off\",\n    \"max-nested-callbacks\": \"error\",\n    \"max-statements\": \"off\",\n    \"max-statements-per-line\": \"off\",\n    \"multiline-comment-style\": \"off\",\n    \"multiline-ternary\": \"off\",\n    \"new-cap\": \"error\",\n    \"new-parens\": \"error\",\n    \"newline-after-var\": \"off\",\n    \"newline-before-return\": \"off\",\n    \"newline-per-chained-call\": \"off\",\n    \"no-alert\": \"error\",\n    \"no-array-constructor\": \"error\",\n    \"no-await-in-loop\": \"off\",\n    \"no-bitwise\": \"off\",\n    \"no-buffer-constructor\": \"error\",\n    \"no-caller\": \"error\",\n    \"no-catch-shadow\": \"error\",\n    \"no-console\": \"off\",\n    \"no-constructor-return\": \"error\",\n    \"no-continue\": \"error\",\n    \"no-div-regex\": \"error\",\n    \"no-duplicate-imports\": \"error\",\n    \"@typescript-eslint/no-empty-function\": \"off\",\n    \"no-eq-null\": \"error\",\n    \"no-eval\": \"error\",\n    \"no-extend-native\": \"error\",\n    \"no-extra-bind\": \"error\",\n    \"no-extra-label\": \"error\",\n    \"no-extra-parens\": \"off\",\n    \"no-floating-decimal\": \"error\",\n    \"no-implicit-coercion\": \"off\",\n    \"no-implicit-globals\": \"error\",\n    \"no-implied-eval\": \"error\",\n    \"no-inline-comments\": \"off\",\n    \"no-invalid-this\": \"error\",\n    \"no-iterator\": \"error\",\n    \"no-label-var\": \"error\",\n    \"no-labels\": \"error\",\n    \"no-lone-blocks\": \"error\",\n    \"no-lonely-if\": \"error\",\n    \"no-loop-func\": \"error\",\n    \"no-loss-of-precision\": \"error\",\n    \"@typescript-eslint/no-magic-numbers\": \"off\",\n    \"no-mixed-operators\": \"off\",\n    \"no-mixed-requires\": \"error\",\n    \"no-multi-assign\": \"error\",\n    \"no-multi-spaces\": \"error\",\n    \"no-multi-str\": \"error\",\n    \"no-multiple-empty-lines\": \"error\",\n    \"no-native-reassign\": \"error\",\n    \"no-negated-condition\": \"off\",\n    \"no-negated-in-lhs\": \"error\",\n    \"no-nested-ternary\": \"error\",\n    \"no-new\": \"error\",\n    \"no-new-func\": \"error\",\n    \"no-new-object\": \"error\",\n    \"no-new-require\": \"error\",\n    \"no-new-wrappers\": \"error\",\n    \"no-nonoctal-decimal-escape\": \"error\",\n    \"no-octal-escape\": \"error\",\n    \"no-param-reassign\": \"error\",\n    \"no-path-concat\": \"error\",\n    \"no-plusplus\": \"off\",\n    \"no-process-env\": \"off\",\n    \"no-process-exit\": \"error\",\n    \"no-promise-executor-return\": \"error\",\n    \"no-proto\": \"error\",\n    \"no-restricted-exports\": \"error\",\n    \"no-restricted-globals\": \"error\",\n    \"no-restricted-imports\": \"error\",\n    \"no-restricted-modules\": \"error\",\n    \"no-restricted-properties\": \"error\",\n    \"no-restricted-syntax\": \"error\",\n    \"no-script-url\": \"error\",\n    \"no-self-compare\": \"error\",\n    \"no-sequences\": \"error\",\n    \"@typescript-eslint/no-shadow\": \"error\",\n    \"no-spaced-func\": \"off\",\n    \"no-sync\": \"error\",\n    \"no-tabs\": \"error\",\n    \"no-template-curly-in-string\": \"error\",\n    \"no-throw-literal\": \"error\",\n    \"no-trailing-spaces\": \"error\",\n    \"no-undef-init\": \"off\",\n    \"no-undefined\": \"off\",\n    \"no-underscore-dangle\": \"off\",\n    \"no-unmodified-loop-condition\": \"error\",\n    \"no-unneeded-ternary\": \"error\",\n    \"no-unreachable-loop\": \"error\",\n    \"no-unsafe-optional-chaining\": \"error\",\n    \"no-unused-expressions\": \"error\",\n    \"@typescript-eslint/no-use-before-define\": \"error\",\n    \"no-useless-backreference\": \"error\",\n    \"no-useless-call\": \"error\",\n    \"no-useless-computed-key\": \"error\",\n    \"no-useless-concat\": \"error\",\n    \"no-useless-constructor\": \"error\",\n    \"no-useless-rename\": \"error\",\n    \"no-useless-return\": \"error\",\n    \"no-var\": \"error\",\n    \"no-void\": \"error\",\n    \"no-warning-comments\": \"error\",\n    \"no-whitespace-before-property\": \"error\",\n    \"nonblock-statement-body-position\": \"error\",\n    \"object-curly-newline\": \"error\",\n    \"object-curly-spacing\": [\"error\", \"always\"],\n    \"object-property-newline\": [\n      \"error\",\n      {\n        \"allowAllPropertiesOnSameLine\": true\n      }\n    ],\n    \"object-shorthand\": \"error\",\n    \"one-var\": [\"error\", \"never\"],\n    \"operator-assignment\": \"error\",\n    \"operator-linebreak\": \"error\",\n    \"padded-blocks\": [\"error\", \"never\"],\n    \"padding-line-between-statements\": \"off\",\n    \"prefer-arrow-callback\": \"error\",\n    \"prefer-const\": \"error\",\n    \"prefer-destructuring\": \"off\",\n    \"prefer-exponentiation-operator\": \"error\",\n    \"prefer-named-capture-group\": \"off\",\n    \"prefer-numeric-literals\": \"error\",\n    \"prefer-object-spread\": \"error\",\n    \"prefer-promise-reject-errors\": \"error\",\n    \"prefer-reflect\": \"error\",\n    \"prefer-regex-literals\": \"error\",\n    \"prefer-rest-params\": \"error\",\n    \"prefer-spread\": \"error\",\n    \"prefer-template\": \"off\",\n    \"quote-props\": \"off\",\n    \"quotes\": \"off\",\n    \"radix\": \"off\",\n    \"require-atomic-updates\": \"error\",\n    \"require-await\": \"error\",\n    \"require-unicode-regexp\": \"off\",\n    \"rest-spread-spacing\": \"error\",\n    \"@typescript-eslint/semi\": [\n      \"error\",\n      \"always\",\n      {\n        \"omitLastInOneLineBlock\": true\n      }\n    ],\n    \"semi-spacing\": \"error\",\n    \"semi-style\": \"error\",\n    \"no-extra-semi\": \"error\",\n    \"space-before-blocks\": \"error\",\n    \"space-before-function-paren\": [\n      \"error\",\n      {\n        \"anonymous\": \"never\",\n        \"named\": \"never\",\n        \"asyncArrow\": \"always\"\n      }\n    ],\n    \"space-in-parens\": [\"error\", \"never\"],\n    \"space-infix-ops\": \"error\",\n    \"space-unary-ops\": [\n      \"error\",\n      {\n        \"words\": true,\n        \"nonwords\": false\n      }\n    ],\n    \"spaced-comment\": \"error\",\n    \"strict\": \"error\",\n    \"switch-colon-spacing\": \"error\",\n    \"symbol-description\": \"error\",\n    \"template-curly-spacing\": \"error\",\n    \"template-tag-spacing\": \"error\",\n    \"unicode-bom\": \"error\",\n    \"valid-jsdoc\": \"error\",\n    \"vars-on-top\": \"error\",\n    \"wrap-iife\": \"error\",\n    \"wrap-regex\": \"off\",\n    \"yield-star-spacing\": \"error\",\n    \"yoda\": \"error\",\n    \"@typescript-eslint/ban-types\": [\"warn\"],\n    \"react/react-in-jsx-scope\": \"off\"\n  }\n}\n"
  },
  {
    "path": "kibbeh/.gitignore",
    "content": "_\n.env.local\n.idea\nsw.js.map"
  },
  {
    "path": "kibbeh/.prettierignore",
    "content": "*.spec.tsx\n*.spec.tsx.snap\nsrc/generated/\n"
  },
  {
    "path": "kibbeh/.prettierrc.js",
    "content": "module.exports = {\n\ttrailingComma: \"es5\",\n\ttabWidth: 2,\n\tsemi: true,\n\tsingleQuote: false,\n\tarrowParens: \"always\",\n};\n"
  },
  {
    "path": "kibbeh/.storybook/main.js",
    "content": "const path = require(\"path\");\n\nconst toPath = (_path) => path.join(process.cwd(), _path);\n\nmodule.exports = {\n    stories: [\"../src/stories/**/*.story.@(ts|tsx|js|jsx|mdx)\"],\n    addons: [\"@storybook/addon-links\", \"@storybook/addon-essentials\", \"@storybook/addon-postcss\"],\n    webpackFinal: async (config, { configType }) => {\n        // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'\n        // You can change the configuration based on that.\n        // 'PRODUCTION' is used when building the static version of storybook.\n\n        // Added to support PostCSS v8.X\n        /**\n         * CSS handling, specifically overriding postcss loader\n         */\n        // Find the only Storybook webpack rule that tests for css\n        const cssRule = config.module.rules.find((rule) =>\n            \"test.css\".match(rule.test)\n        );\n        // Which loader in this rule mentions the custom Storybook postcss-loader?\n        const loaderIndex = cssRule.use.findIndex((loader) => {\n            // Loaders can be strings or objects\n            const loaderString = typeof loader === \"string\" ? loader : loader.loader;\n            // Find the first mention of \"postcss-loader\", it may be in a string like:\n            // \"@storybook/core/node_modules/postcss-loader\"\n            return loaderString.includes(\"postcss-loader\");\n        });\n        // Simple loader string form, removes the obsolete \"options\" key\n        cssRule.use[loaderIndex] = \"postcss-loader\";\n\n        // SVG\n        // Needed for SVG importing using svgr\n        const indexOfRuleToRemove = config.module.rules.findIndex(\n            (rule) => rule.test && rule.test.toString().includes(\"svg\")\n        );\n\n        config.module.rules.splice(indexOfRuleToRemove, 1, {\n            test: /\\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\\?.*)?$/,\n            loader: require.resolve(\"file-loader\"),\n            options: {\n                name: \"static/media/[name].[hash:8].[ext]\",\n                esModule: false,\n            },\n        });\n        config.module.rules.push({\n            test: /\\.svg$/,\n            use: [\n                {\n                    loader: \"@svgr/webpack\",\n                    options: {\n                        svgo: false,\n                    },\n                },\n            ],\n        });\n\n        return {\n            ...config,\n            resolve: {\n                ...config.resolve,\n                alias: {\n                    ...config.resolve.alias,\n                    //          \"@emotion/core\": toPath(\"node_modules/@emotion/react\"),\n                    //          \"@emotion/styled\": toPath(\"node_modules/@emotion/styled\"),\n                    //          \"emotion-theming\": toPath(\"node_modules/@emotion/react\")\n                },\n            },\n        };\n    },\n};"
  },
  {
    "path": "kibbeh/.storybook/manager.js",
    "content": "import { addons } from \"@storybook/addons\";\nimport { create } from \"@storybook/theming\";\n\naddons.setConfig({\n    theme: create({\n        base: \"dark\",\n        brandTitle: \"DogeBook\",\n    }),\n});"
  },
  {
    "path": "kibbeh/.storybook/preview-head.html",
    "content": "<div id=\"docs-root\"></div>\n\n<style>\n  .sbdocs.sbdocs-wrapper.css-zgt7u1 {\n    overflow-y: scroll;\n    display: flex;\n    -webkit-box-pack: center;\n    flex: 1 1 auto;\n    justify-content: center;\n    padding: 4rem 20px;\n    -webkit-text-emphasis-position: over;\n    ight: 100vh;\n    height: 100vh;\n    box-sizing: border-box;\n  }\n</style>\n"
  },
  {
    "path": "kibbeh/.storybook/preview.js",
    "content": "import \"../src/styles/globals.css\";\nimport { addDecorator } from \"@storybook/react\";\nimport { init_i18n } from \"../src/lib/i18n\";\nimport { QueryClientProvider, QueryClient } from \"react-query\";\n\nlet hasInit = false;\nconst client = new QueryClient();\n\naddDecorator((storyFn) => {\n  if (!hasInit) {\n    init_i18n();\n    hasInit = true;\n  }\n  return <QueryClientProvider client={client}>{storyFn()}</QueryClientProvider>;\n});\n\nexport const parameters = {\n  backgrounds: {\n    default: \"bg-on-figma\",\n    values: [\n      {\n        name: \"bg-on-figma\",\n        value: \"#0b0e11\",\n      },\n      {\n        name: \"black\",\n        value: \"#000\",\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "kibbeh/.stylelintrc.json",
    "content": "{\n  \"extends\": \"stylelint-config-standard\",\n  \"rules\": {\n    \"selector-descendant-combinator-no-non-space\": null,\n    \"indentation\": null\n  }\n}\n"
  },
  {
    "path": "kibbeh/README.md",
    "content": "<p align=\"center\">\n    <img height=100 src=\"https://raw.githubusercontent.com/benawad/dogehouse/staging/.redesign-assets/dogehouse_logo.svg\"/>\n</p>\n\n<p align=\"center\">\n    <strong>Taking voice conversations to the moon 🚀</strong>\n</p>\n\n<p align=\"center\">\n    <img src=\"https://img.shields.io/github/contributors/benawad/dogehouse\"/>\n    <img src=\"https://img.shields.io/discord/810571477316403233?label=discord\"/>\n    <img src=\"https://img.shields.io/github/v/release/benawad/dogehouse\"/>\n</p>\n<br/>\n\n\n# What is this folder?\n\nThis folder is called kibbeh ([/ˈkɪbi/](https://en.wikipedia.org/wiki/Kibbeh)), it is currently used for our Next.js frontend.\n\nIt's live on 👉 [dogehouse.tv](https://dogehouse.tv)\n\n# How can I contribute?\n\n**We're using [Yarn](https://yarnpkg.com/) for this project, do not use npm for the following commands**\n\nCompile @dogehouse/kebab by executing the following commands:\n\n```bash\ncd ../kebab\nyarn\nyarn build\n```\n\nAfter you successfully compiled Kebab, go back to this directory and install all modules (@dogehouse/kebab is a yarn workspace, you do <u>not</u> need to manually copy it to node_modules)\n\nYou should now be all set to go, go ahead and run the dev server\n\n```bash\ncd ../kibbeh\nyarn\nyarn staging\n```\n\n> If for some reason the above commands dont work and leave you with a `@dogehouse/kebab` module not found error, just copy the entire `dogehouse/kebab` directory over to `dogehouse/node_modules/@dogehouse/kebab`\n\n> **NOTE:** OAuth login is kinda broken as of right now *(if running using `yarn staging`)* so to get around it follow the steps below:\n> - Enter your credentials in OAuth\n> - You should now reach a page saying `not found`. Copy the url of the page\n> - Edit the previously copied URL and change the protocol from `https` to `http` and the hostname from `doge-staging.stripcode.dev` to `localhost:3000`. For example: `https://doge-staging.stripcode.dev/?accessToken=YOUR_TOKEN_HERE` would be changed to  `http://localhost:3000/?accessToken=YOUR_TOKEN_HERE`\n> - You should now be logged in.  \n\n> Also to prevent repeating the steps mentioned above, instead of logging in just directly go to `http://localhost:3000/dash` and it should read your tokens from localstorage and log you in\n"
  },
  {
    "path": "kibbeh/cypress/.gitignore",
    "content": "videos\nscreenshots\n"
  },
  {
    "path": "kibbeh/cypress/fixtures/example.json",
    "content": "{\n  \"name\": \"Using fixtures to represent data\",\n  \"email\": \"hello@cypress.io\",\n  \"body\": \"Fixtures are a great way to mock data for responses to routes\"\n}\n"
  },
  {
    "path": "kibbeh/cypress/integration/create-scheduled-room.ts",
    "content": "import {\n    defaultRoomName,\n    defaultTestUsername,\n} from \"../support/test-constants\";\n\ndescribe(\"create scheduled room then\", () => {\n    before(() => {\n        cy.loginTestUser();\n        cy.dataTestId(\"create-scheduled-room\").click();\n        cy.byName(\"name\").type(defaultRoomName);\n        cy.clickSubmit();\n    });\n    it(\"verify scheduled room has been created\", () => {\n        cy.dataTestId(\"view-scheduled-rooms\").click();\n        cy.dataTestId(`scheduledroom:name:${defaultRoomName}`);\n    });\n\n    // it(\"verify scheduled room shows up on user profile\", () => {\n    // for somereason this \"dropdown-trigger\" is being found more than once and cypress dosent wanna click it\n    //     cy.dataTestId(\"dropdown-trigger\").click();\n    //     cy.dataTestId(\"profile-link\").click();\n    //     cy.dataTestId(`user:${defaultTestUsername}:tab:scheduled`).click();\n    //     cy.dataTestId(`scheduledroom:name:${defaultRoomName}`);\n    // });\n\n});\n"
  },
  {
    "path": "kibbeh/cypress/integration/edit-profile.ts",
    "content": "import {defaultAvatarUrl, defaultBannerUrl, defaultBio} from \"../support/test-constants\";\n\ndescribe(\"edit profile\", () => {\n  before(() => {\n    cy.loginTestUser();\n  });\n  it(\"from feed\", () => {\n    cy.dataTestId(\"edit-profile-widget\").click();\n    cy.byName(\"avatarUrl\").clear().type(defaultAvatarUrl);\n    cy.byName(\"bannerUrl\").clear().type(defaultBannerUrl);\n    cy.byName(\"bio\").clear().type(defaultBio);\n    cy.clickSubmit();\n    cy.dataTestId(\"current-user:bio\").invoke(\"text\").should(\"eq\", defaultBio);\n  });\n});\n"
  },
  {
    "path": "kibbeh/cypress/integration/room-creator.ts",
    "content": "import {\n  defaultRoomName,\n  defaultTestUsername,\n} from \"../support/test-constants\";\n\ndescribe(\"create room then\", () => {\n  before(() => {\n    cy.loginTestUser();\n    cy.dataTestId(\"feed-action-button\").click();\n    cy.byName(\"name\").type(defaultRoomName);\n    cy.clickSubmit();\n  });\n  it(\"verify room name and creator name\", () => {\n    cy.dataTestId(\"room-title\").invoke(\"text\").should(\"eq\", defaultRoomName);\n    cy.dataTestId(`room:user:node:${defaultTestUsername}`);\n  });\n  it(\"mute\", () => {\n    cy.dataTestId(\"mute\").click();\n    cy.dataTestId(`muted:${defaultTestUsername}`);\n    cy.dataTestId(\"mute\").click();\n    cy.dataTestId(`muted:${defaultTestUsername}`).should(\"not.exist\");\n  });\n  it(\"deafen\", () => {\n    cy.dataTestId(\"deafen\").click();\n    cy.dataTestId(`deafened:${defaultTestUsername}`);\n    cy.dataTestId(\"deafen\").click();\n    cy.dataTestId(`deafened:${defaultTestUsername}`).should(\"not.exist\");\n  });\n  it(\"deafen/mute sequence\", () => {\n    cy.testDeafenSequence();\n    cy.dataTestId(`deafened:${defaultTestUsername}`).should(\"not.exist\");\n  });\n  it(\"invite friends\", () => {\n    cy.dataTestId(\"invite-friends\").click();\n    cy.dataTestId(\"container\");\n    cy.go(\"back\");\n  });\n  it(\"settings\", () => {\n    cy.dataTestId(\"room-settings\").click();\n    cy.closeModal();\n  });\n  it(\"profile modal\", () => {\n    cy.dataTestId(`room:user:node:${defaultTestUsername}`).click();\n    cy.dataTestId(\"profile-info-username\")\n      .invoke(\"text\")\n      .should(\"eq\", \"@\" + defaultTestUsername);\n    cy.closeModal();\n  });\n  it(\"minimized room widget deafen/mute sequence desktop\", () => {\n    cy.viewport(2560, 1440);\n    cy.dataTestId(`logo-link`).click();\n    cy.testDeafenSequence();\n  });\n  it(\"floating room widget deafen/mute sequence\", () => {\n    cy.viewport(\"iphone-x\");\n    cy.dataTestId(\"floating-room-container\").should(\"exist\");\n    cy.testDeafenSequence();\n    cy.dataTestId(`room-card:${defaultRoomName}`).click();\n  });\n  it(\"leave room\", () => {\n    cy.dataTestId(\"leave-room\").click();\n  });\n});\n"
  },
  {
    "path": "kibbeh/cypress/integration/search.ts",
    "content": "import { defaultTestUsername } from \"../support/test-constants\";\n\ndescribe(\"searchbar\", () => {\n  before(() => {\n    cy.loginTestUser();\n  });\n  it(\"search\", () => {\n    cy.dataTestId(\"searchbar\").type(\"@\" + defaultTestUsername);\n    cy.dataTestId(`search:user:${defaultTestUsername}`).click();\n    cy.dataTestId(\"profile-info-username\")\n      .invoke(\"text\")\n      .should(\"eq\", \"@\" + defaultTestUsername);\n  });\n});\n"
  },
  {
    "path": "kibbeh/cypress/plugins/index.js",
    "content": "/// <reference types=\"cypress\" />\n// ***********************************************************\n// This example plugins/index.js can be used to load plugins\n//\n// You can change the location of this file or turn off loading\n// the plugins file with the 'pluginsFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/plugins-guide\n// ***********************************************************\n\n// This function is called when a project is opened or re-opened (e.g. due to\n// the project's config changing)\n\n/**\n * @type {Cypress.PluginConfig}\n */\n// eslint-disable-next-line no-unused-vars\nmodule.exports = (on, config) => {\n  // `on` is used to hook into various events Cypress emits\n  // `config` is the resolved Cypress config\n}\n"
  },
  {
    "path": "kibbeh/cypress/plugins/sample_spec.js",
    "content": ""
  },
  {
    "path": "kibbeh/cypress/support/commands.ts",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add('login', (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This will overwrite an existing command --\n// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })\n\nimport { defaultTestUsername } from \"./test-constants\";\n\nCypress.Commands.add(\"dataTestId\", (value, wait = false) => {\n  const selector = `[data-testid=\"${value}\"]`;\n  if (wait) {\n    return cy.waitFor(selector);\n  } else {\n    return cy.get(selector);\n  }\n});\n\nCypress.Commands.add(\"closeModal\", () => {\n  return cy.get(`[data-testid=\"close-modal\"]`).click();\n});\n\nCypress.Commands.add(\"byName\", (value) => {\n  return cy.get(`[name=${value}]`);\n});\n\nCypress.Commands.add(\"clickSubmit\", () => {\n  return cy.get(`button[type=\"submit\"]`).click();\n});\n\nCypress.Commands.add(\"testDeafenSequence\", () => {\n  // deafen -> mute will undeafen\n  cy.dataTestId(\"deafen\").click();\n  cy.dataTestId(`mic-off`);\n  cy.dataTestId(`headphone-off`);\n  cy.dataTestId(\"mute\").click();\n  cy.dataTestId(`mic-on`);\n  cy.dataTestId(`headphone-on`);\n  // mute -> deafen -> mute will undeafen\n  cy.dataTestId(\"mute\").click();\n  cy.dataTestId(`mic-off`);\n  cy.dataTestId(\"deafen\").click();\n  cy.dataTestId(`mic-off`);\n  cy.dataTestId(`headphone-off`);\n  cy.dataTestId(\"mute\").click();\n  cy.dataTestId(`mic-on`);\n  cy.dataTestId(`headphone-on`);\n});\n\nCypress.Commands.add(\"loginTestUser\", (value = defaultTestUsername) => {\n  cy.viewport(2560, 1440);\n  cy.visit(\"/\", {\n    onBeforeLoad(win) {\n      cy.stub(win, \"prompt\").returns(value);\n    },\n  });\n\n  return cy.dataTestId(\"create-test-user\").click();\n});\n"
  },
  {
    "path": "kibbeh/cypress/support/index.d.ts",
    "content": "// load type definitions that come with Cypress module\n// eslint-disable-next-line spaced-comment\n/// <reference types=\"cypress\" />\n\ndeclare namespace Cypress {\n  interface Chainable {\n    dataTestId(value: string, wait?: boolean): Chainable<Element>;\n    byName(value: string): Chainable<Element>;\n    clickSubmit(): Chainable<Element>;\n    closeModal(): Chainable<Element>;\n    loginTestUser(value?: string): Chainable<AUTWindow>;\n    testDeafenSequence(): void;\n  }\n}\n"
  },
  {
    "path": "kibbeh/cypress/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport \"./commands\";\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "kibbeh/cypress/support/test-constants.ts",
    "content": "export const defaultTestUsername = \"cypress_user\";\nexport const defaultRoomName = \"DogeHouse Acquisition Discussion\";\nexport const defaultAvatarUrl =\n  \"https://pbs.twimg.com/profile_images/1152793238761345024/VRBvxeCM_400x400.jpg\";\nexport const defaultBannerUrl = \"https://pbs.twimg.com/profile_banners/840626569743912960/1601562221/600x200\";\nexport const defaultBio = \"I am a good description, I promise.\";\n"
  },
  {
    "path": "kibbeh/cypress/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"sourceMap\": true,\n    \"target\": \"es6\",\n    \"jsx\": \"preserve\",\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true,\n    \"noEmitOnError\": false,\n    \"resolveJsonModule\": true,\n    \"importHelpers\": true,\n    \"strict\": true,\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"types\": [\"cypress\"],\n    \"module\": \"commonjs\",\n    \"esModuleInterop\": true,\n    \"preserveSymlinks\": true,\n    \"typeRoots\": [\"./node_modules/@types\"],\n    \"downlevelIteration\": true,\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true\n    // \"baseUrl\": \"src\"\n  },\n  \"include\": [\"./**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "kibbeh/cypress.json",
    "content": "{\n  \"baseUrl\": \"http://localhost:3000\",\n  \"defaultCommandTimeout\": 20000,\n  \"env\": {\n    \"apiBaseUrl\": \"http://localhost:4001\"\n  },\n  \"video\": false,\n  \"screenshotOnRunFailure\": false\n}\n"
  },
  {
    "path": "kibbeh/deploy.sh",
    "content": "#!/bin/bash\n\nset -e\n\ngit pull origin staging\ngit checkout prod\ngit merge staging\ngit push origin prod\ngit checkout staging"
  },
  {
    "path": "kibbeh/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/types/global\" />\n"
  },
  {
    "path": "kibbeh/next.config.js",
    "content": "const withTM = require(\"next-transpile-modules\")([\"@dogehouse/kebab\"]);\n\nmodule.exports = withTM({\n  future: {\n    webpack5: true,\n  },\n  reactStrictMode: true,\n  typescript: {\n    // !! WARN !!\n    // Dangerously allow production builds to successfully complete even if\n    // your project has type errors.\n    // @todo remove this once storybook is fixed\n    // !! WARN !!\n    // ignoreBuildErrors: true,\n  },\n});\n"
  },
  {
    "path": "kibbeh/package.json",
    "content": "{\n  \"name\": \"@dogehouse/kibbeh\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"staging\": \"cross-env NEXT_PUBLIC_IS_STAGING=true NEXT_PUBLIC_API_BASE_URL=https://doge-staging.stripcode.dev next dev\",\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"compile\": \"tsc\",\n    \"sync:i18\": \"ts-node --project scripts/tsconfig.json scripts/syncTranslations.ts\",\n    \"i18\": \"yarn sync:i18\",\n    \"lint\": \"eslint src\",\n    \"format\": \"prettier --write src\",\n    \"storybook\": \"start-storybook -p 6006 -s ./public\",\n    \"build-storybook\": \"build-storybook -s ./public\",\n    \"convert-icons\": \"svgr ../.redesign-assets/mobile/icons --replace-attr-values '#fff'='currentColor' --replace-attr-values 'none'='currentColor' --typescript --out-dir src/icons\",\n    \"cy:install\": \"cypress install\",\n    \"cy:run\": \"cypress run --headless --browser chrome\",\n    \"cy:open\": \"cypress open --browser chrome\",\n    \"test\": \"jest\",\n    \"test:ci\": \"jest --ci\",\n    \"test:watch\": \"jest --watch\",\n    \"test:e2e:ci\": \"start-server-and-test dev http://localhost:3000 cy:run\"\n  },\n  \"dependencies\": {\n    \"@date-io/date-fns\": \"^1.3.13\",\n    \"@dogehouse/kebab\": \"workspace:kebab\",\n    \"@material-ui/core\": \"^4.11.4\",\n    \"@material-ui/pickers\": \"^3.3.10\",\n    \"@svgr/webpack\": \"^5.5.0\",\n    \"@tailwindcss/line-clamp\": \"^0.2.0\",\n    \"date-fns\": \"^2.21.1\",\n    \"downshift\": \"^6.1.3\",\n    \"emoji-regex\": \"^9.2.2\",\n    \"formik\": \"^2.2.6\",\n    \"grapheme-splitter\": \"^1.0.4\",\n    \"hark\": \"^1.2.3\",\n    \"i18next\": \"^20.2.2\",\n    \"i18next-browser-languagedetector\": \"^6.1.0\",\n    \"i18next-http-backend\": \"^1.2.2\",\n    \"is-electron\": \"^2.2.0\",\n    \"isomorphic-fetch\": \"^3.0.0\",\n    \"lodash\": \"^4.17.21\",\n    \"mediasoup-client\": \"^3.6.30\",\n    \"next\": \"10.2.0\",\n    \"normalize-url\": \"4\",\n    \"nprogress\": \"^0.2.0\",\n    \"rc-slider\": \"^9.7.2\",\n    \"react\": \"17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-draggable\": \"^4.4.3\",\n    \"react-feather\": \"^2.0.9\",\n    \"react-hotkeys\": \"^2.0.0\",\n    \"react-i18next\": \"^11.8.15\",\n    \"react-modal\": \"^3.13.1\",\n    \"react-popper\": \"^2.2.5\",\n    \"react-query\": \"^3.15.0\",\n    \"react-responsive\": \"^8.2.0\",\n    \"react-spring\": \"^9.1.2\",\n    \"react-use-gesture\": \"^9.1.3\",\n    \"react-virtual\": \"^2.7.1\",\n    \"superstruct\": \"^0.15.1\",\n    \"tslib\": \"^2.2.0\",\n    \"twemoji-parser\": \"^13.0.0\",\n    \"use-debounce\": \"^6.0.1\",\n    \"uuid\": \"^8.3.2\",\n    \"wakelock-lazy-polyfill\": \"^1.0.0\",\n    \"zustand\": \"^3.4.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/runtime\": \"^7.13.17\",\n    \"@storybook/addon-essentials\": \"^6.2.9\",\n    \"@storybook/addon-links\": \"^6.2.9\",\n    \"@storybook/addon-postcss\": \"^2.0.0\",\n    \"@storybook/react\": \"^6.2.9\",\n    \"@storybook/theming\": \"^6.2.9\",\n    \"@svgr/cli\": \"^5.5.0\",\n    \"@tailwindcss/line-clamp\": \"^0.2.0\",\n    \"@testing-library/jest-dom\": \"^5.12.0\",\n    \"@testing-library/react\": \"^11.2.6\",\n    \"@types/emoji-mart\": \"^3.0.4\",\n    \"@types/hark\": \"^1.2.1\",\n    \"@types/isomorphic-fetch\": \"^0.0.35\",\n    \"@types/jest\": \"^26.0.23\",\n    \"@types/lodash\": \"^4.14.168\",\n    \"@types/node\": \"^14.14.43\",\n    \"@types/nprogress\": \"^0.2.0\",\n    \"@types/react\": \"^17.0.4\",\n    \"@types/react-dom\": \"^17.0.3\",\n    \"@types/react-i18next\": \"^8.1.0\",\n    \"@types/react-modal\": \"^3.12.0\",\n    \"@types/react-responsive\": \"^8.0.2\",\n    \"@types/superstruct\": \"^0.8.2\",\n    \"@types/twemoji\": \"^12.1.1\",\n    \"@types/twemoji-parser\": \"^12.1.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.22.0\",\n    \"@typescript-eslint/parser\": \"^4.22.0\",\n    \"autoprefixer\": \"^10.2.5\",\n    \"babel-jest\": \"^26.6.3\",\n    \"babel-loader\": \"^8.2.2\",\n    \"cross-env\": \"^7.0.3\",\n    \"css-loader\": \"^5.2.4\",\n    \"cypress\": \"^7.2.0\",\n    \"eslint\": \"^7.25.0\",\n    \"eslint-plugin-cypress\": \"^2.11.2\",\n    \"eslint-plugin-react\": \"^7.23.2\",\n    \"eslint-plugin-react-hooks\": \"^4.2.0\",\n    \"jest\": \"^26.6.3\",\n    \"mutationobserver-shim\": \"^0.3.7\",\n    \"next-transpile-modules\": \"^7.0.0\",\n    \"postcss\": \"^8.2.13\",\n    \"postcss-import\": \"^14.0.1\",\n    \"postcss-loader\": \"v4.2.0\",\n    \"prettier\": \"^2.2.1\",\n    \"start-server-and-test\": \"^1.12.1\",\n    \"style-loader\": \"^2.0.0\",\n    \"stylelint\": \"^13.13.0\",\n    \"stylelint-config-standard\": \"^22.0.0\",\n    \"tailwind-scrollbar\": \"^1.3.0\",\n    \"tailwindcss\": \"^2.1.2\",\n    \"ts-node\": \"9.1.1\",\n    \"typescript\": \"4.2.4\"\n  }\n}\n"
  },
  {
    "path": "kibbeh/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "kibbeh/public/locales/af/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Laai Meer\",\n    \"loading\": \"Laai Tans...\",\n    \"noUsersFound\": \"geen gebruikers gevind nie\",\n    \"ok\": \"ok\",\n    \"yes\": \"ja\",\n    \"no\": \"nee\",\n    \"cancel\": \"kanselleer\",\n    \"save\": \"stoor\",\n    \"edit\": \"wysig\",\n    \"delete\": \"verwyder\",\n    \"joinRoom\": \"sluit aan\",\n    \"copyLink\": \"kopieër skakel\",\n    \"copied\": \"gekopieër\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please give DogeHouse Accessibility permessions\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Gedemp | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Oorsprong Storie\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Meld 'n fout aan\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"verbod\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lys van gebruikers wie nie in private kamers is nie, en vir wie jy volg\",\n      \"currentRoom\": \"tans in:\",\n      \"startPrivateRoom\": \"Begin 'n private kamer saam met hulle\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Skep Kamer\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"kamer verwyder, gaan terug\",\n      \"shareRoomLink\": \"deel skakel na kamer\",\n      \"inviteFollowers\": \"Jy kan jou volgers nooi wie aanlyn is\",\n      \"whenFollowersOnline\": \"Wanneer jou volgers aanlyn is, sal hulle hier verskyn\"\n    },\n    \"login\": {\n      \"headerText\": \"Ons neem stemgesprekke na die maan 🚀\",\n      \"featureText_1\": \"Donker tema\",\n      \"featureText_2\": \"Oop aansluitings\",\n      \"featureText_3\": \"Multi-platform ondersteuning\",\n      \"featureText_4\": \"Oop Bron\",\n      \"featureText_5\": \"teks-klets\",\n      \"featureText_6\": \"Aangedryf deur Doge\",\n      \"loginGithub\": \"Teken aan met GitHub\",\n      \"loginTwitter\": \"Teken aan met Twitter\",\n      \"createTestUser\": \"Maak toets gebruiker\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"teken uit\",\n      \"probablyLoading\": \"waarskynlik besig om te laai...\",\n      \"voiceSettings\": \"gaan na stem instellings\",\n      \"soundSettings\": \"gaan na klank instellings\",\n      \"deleteAccount\": \"verwyder rekening\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oeps! Hierdie bladsy het verlore geraak in gesprek.\",\n      \"goHomeMessage\": \"Moenie kommer nie. Jy kan\",\n      \"goHomeLinkText\": \"gaan huistoe\"\n    },\n    \"room\": {\n      \"speakers\": \"Sprekers\",\n      \"requestingToSpeak\": \"Versoek om te praat\",\n      \"listeners\": \"Luisteraars\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"soek...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Klanke\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"wysig profiel\",\n      \"followsYou\": \"volg jou\",\n      \"followers\": \"volgelinge\",\n      \"following\": \"volg\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Stem instellings\",\n      \"mic\": \"mikrofoon:\",\n      \"permissionError\": \"Geen mikrofone gevind nie. Jy het óf geen ingeprop nie, of jy het nie vir hierdie webtuiste toestemming gegee nie.\",\n      \"refresh\": \"herlaai mikrofoon lys\",\n      \"volume\": \"volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\",\n      \"errorMsg\": \"Please enter valid app title\",\n      \"label\": \"Enter app title\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Verbande gebruikers\",\n      \"unban\": \"Ontban\",\n      \"noBans\": \"Niemand is al verban nie\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Verlaat huidige kamer\",\n      \"confirmLeaveRoom\": \"Is jy seker jy wil waai?\",\n      \"leave\": \"Verlaat\",\n      \"inviteUsersToRoomBtn\": \"Nooi gebruikers na kamer\",\n      \"invite\": \"Nooi\",\n      \"toggleMuteMicBtn\": \"Wissel demp mikrofoon\",\n      \"mute\": \"Demp\",\n      \"unmute\": \"Ontdemp\",\n      \"makeRoomPublicBtn\": \"Maak kamer publiek!\",\n      \"settings\": \"Instellings\",\n      \"speaker\": \"Sprekers\",\n      \"listener\": \"Luisteraar\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Jou toestel word tans nie ondersteun nie. Jy kan 'n\",\n      \"linkText\": \"kwessie op GitHub\",\n      \"addSupport\": \"meld en ons sal probeer om ondersteuning vir jou testel by te voeg.\"\n    },\n    \"inviteButton\": { \"invited\": \"Genooi\", \"inviteToRoom\": \"Nooi na kamer\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Toestemming ontken om jou mikrofoon te gebruik (jy mag dalk in jou blaaier instellings die bladsy te moet herlaai)\",\n      \"dismiss\": \"ontslaan\",\n      \"tryAgain\": \"probeer weer\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"stel sleutelbind\",\n      \"listening\": \"luister\",\n      \"toggleMuteKeybind\": \"wissel demp sleutelbind\",\n      \"togglePushToTalkKeybind\": \"wissel push-to-talk sleutelbind\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"geen klank verbruiker vir een of ander rede\"\n    },\n    \"addToCalendar\": { \"add\": \"Voeg in kalender\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was vernietig deur die bediener. Dit gebeur gewoonlik as jy die webtuiste oopmaak in 'n ander oortjie.\",\n      \"reconnect\": \"verbind weer\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"publiek\",\n        \"private\": \"privaat\",\n        \"roomName\": \"kamer naam\",\n        \"roomDescription\": \"kamer beskrying\",\n        \"descriptionError\": \"maximum lengte 500\",\n        \"nameError\": \"moet tussen 2 en 60 karakters lank wees\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nuwe kamer geskep\",\n        \"roomInviteFrom\": \"Kamer uitnodiging van\",\n        \"justStarted\": \"Hul het tans begin\",\n        \"likeToJoin\": \", wil jy graag inskakel?\",\n        \"inviteReceived\": \"jy is genooi na\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"gebruikersnaam geneem\",\n        \"avatarUrlError\": \"Ongeldige foto\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar skakel\",\n        \"displayNameError\": \"lengte 2 tot 50 karakters\",\n        \"displayNameLabel\": \"Vertoonnaam\",\n        \"usernameError\": \"Lengte 4 tot 15 karakters en slegte alfanumeries/onderstreep\",\n        \"usernameLabel\": \"gebruikersnaam\",\n        \"bioError\": \"maximum lengte van 160 karakters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Is jy seker jy wil hierdie gebruiker verban van hierdie kamer (en enige nuwe kamers van jou)?\",\n        \"blockUser\": \"verban gebruiker\",\n        \"makeMod\": \"maak mod\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"voeg by as spreker\",\n        \"moveToListener\": \"skuif na luisteraar\",\n        \"banFromChat\": \"verban van gesprek\",\n        \"banFromRoom\": \"verban van kamer\",\n        \"goBackToListener\": \"gaan terug na luisteraar\",\n        \"deleteMessage\": \"verwyder hierdie boodskap\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"benodig toestemming om te praat\",\n        \"makePublic\": \"maak kamer publiek\",\n        \"makePrivate\": \"maak kamer privaat\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Geskeduleerde kamers\",\n      \"noneFound\": \"geen gevind nie\",\n      \"allRooms\": \"alle geskeduleerde kamers\",\n      \"myRooms\": \"my geskeduleerde kamers\",\n      \"scheduleRoomHeader\": \"Geskeduleerde kamer\",\n      \"startRoom\": \"begin kamer\",\n      \"modal\": {\n        \"needsFuture\": \"moet in die toekoms wees\",\n        \"roomName\": \"kamer naam\",\n        \"minLength\": \"min lengte 2\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Gesels\",\n      \"emotesSoon\": \"[emotes binnekort beskikbaar]\",\n      \"bannedAlert\": \"Jy is verban van hierdie gesprek\",\n      \"waitAlert\": \"Jy moet 'n sekonde wag voor jy nog 'n boodskap stuur\",\n      \"search\": \"Soek\",\n      \"searchResults\": \"Soekresultate\",\n      \"recent\": \"Gereeld gebruik\",\n      \"sendMessage\": \"Stuur 'n boodskap\",\n      \"whisper\": \"Fluister\",\n      \"welcomeMessage\": \"Welkom by die gesprek!\",\n      \"roomDescription\": \"Kamerbeskrywing\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/am/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"ተጨማሪ ይጫኑ\",\n    \"loading\": \"በመጫን ላይ ...\",\n    \"noUsersFound\": \"ምንም ተጠቃሚዎች አልተገኙም\",\n    \"ok\": \"እሺ\",\n    \"yes\": \"እሺ\",\n    \"no\": \"አይ\",\n    \"cancel\": \"መተው\",\n    \"save\": \"ሰጥ\",\n    \"edit\": \"ለወጥ\",\n    \"delete\": \"ሰርዝ\",\n    \"joinRoom\": \"ክፍል መቀላቀል\",\n    \"copyLink\": \"ሊንክ ውሰድ\",\n    \"copied\": \"ተቀድቷል!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please give DogeHouse Accessibility permessions\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"ድምፀ-ከል | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"ምንጭ ታሪክ\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"ችግር ሪፖርት\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"አግድ\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"የግል ክፍል ውስጥ ናቸው እና የሚከተሉ ተጠቃሚዎች ዝርዝር፡፡\",\n      \"currentRoom\": \"በአሁኑ ጊዜ ውስጥ:\",\n      \"startPrivateRoom\": \"ከእነሱ ጋር አንድ የግል ክፍል መጀመር\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"ክፍል ፍጠር\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ክፍሉ ጠፋ, ተመለስ\",\n      \"shareRoomLink\": \"የክፍሉን ሊንክ አጋራ\",\n      \"inviteFollowers\": \"መስመር ላይ ናቸው የእርስዎ ተከታዮች መጋበዝ ይችላሉ:\",\n      \"whenFollowersOnline\": \"የእርስዎ ተከታዮች መስመር ላይ ሲሆኑ, እነሱም እዚህ ይታያሉ፡፡\"\n    },\n    \"login\": {\n      \"headerText\": \"ወደ ጨረቃ ድምፅ ውይይቶች መውሰድ 🚀\",\n      \"featureText_1\": \"ጨለማ ገጽታ\",\n      \"featureText_2\": \"ለሁሉም ክፍት ኣገባብ\",\n      \"featureText_3\": \"ብዝሃተ-ስርዓት ድጋፍ\",\n      \"featureText_4\": \"ክፍት ምንጭ\",\n      \"featureText_5\": \"የፅሁፍ ውይይት\",\n      \"featureText_6\": \"በ Doge የተጎላበተ\",\n      \"loginGithub\": \"በ GitHub ግባ\",\n      \"loginTwitter\": \"በ Twitter ግባ\",\n      \"createTestUser\": \"የሙከራ ተጠቃሚ ይፍጠሩ\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"ውጣ\",\n      \"probablyLoading\": \"ምናልባት በመጫን ላይ ...\",\n      \"voiceSettings\": \"የአወራር ድምጽ ቅንብሮች\",\n      \"soundSettings\": \"የድምጽ ቅንብሮች\",\n      \"deleteAccount\": \"መለያህን ደልት\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"ውይ ውይ! ይህ ገጽ ውይይት ውስጥ ጠፋ፡፡\",\n      \"goHomeMessage\": \"መጨነቅ አይደለም፡፡ ትችላለህ\",\n      \"goHomeLinkText\": \"ወደቤት ሂድ\"\n    },\n    \"room\": {\n      \"speakers\": \"ተናጋሪዎች\",\n      \"requestingToSpeak\": \"ለመናገር ፈቃድ ጠይቅ\",\n      \"listeners\": \"አድማጮች\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"ፈልግ ...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"ድምፆች\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"መገለጫ ቀይር\",\n      \"followsYou\": \"እርስዎን ይከተላል\",\n      \"followers\": \"ተከታዮች\",\n      \"following\": \"የሚከተሉት\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"የድምፅ ቅንብሮች\",\n      \"mic\": \"ማይክሮፎን:\",\n      \"permissionError\": \"ምንም የስቴሪዮ አገኘ, ወይ አንዳቸውም ሲሰካ ወይም በዚህ ድር ጣቢያ ፍቃድ የተሰጠው አልቻሉም፡፡\",\n      \"refesh\": \"አድስ ማይክሮፎኑን ዝርዝር\",\n      \"volume\": \"ድምጽ:\",\n      \"refresh\": \"refresh mic list\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\",\n      \"errorMsg\": \"Please enter valid app title\",\n      \"label\": \"Enter app title\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"የታገዱ ተጠቃሚዎች\",\n      \"unban\": \"የታገደውን መልስ\",\n      \"noBans\": \"ማንም ገና የታገደ የለም\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"የአሁኑ ክፍል ይውጡ\",\n      \"confirmLeaveRoom\": \"አንተ ለመውጣት እንደሚፈልጉ እርግጠኛ ነዎት?\",\n      \"leave\": \"ውጣ\",\n      \"inviteUsersToRoomBtn\": \"ክፍል ተጠቃሚዎችን ይጋብዙ\",\n      \"invite\": \"ጋብዝ\",\n      \"toggleMuteMicBtn\": \"ቀያይር ድምጸ ማይክሮፎን\",\n      \"mute\": \"ድምጸ አጥፋ\",\n      \"unmute\": \"ድምጸ ምልስ\",\n      \"makeRoomPublicBtn\": \"ክፍሉን ልሁሉም ክፍት አድርግ!\",\n      \"settings\": \"ቅንብሮች\",\n      \"speaker\": \"ተናጋሪ\",\n      \"listener\": \"አድማጭ\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"የእርስዎ መሣሪያ በአሁኑ ጊዜ አይደገፍም፡፡ ቸግሩን\",\n      \"linkText\": \"በ GitHub ላይ ተናገሩ\",\n      \"addSupport\": \"እኔም ለመሣሪያዎ ድጋፍ ለማከል አሞክራልሁ፡፡\"\n    },\n    \"inviteButton\": { \"invited\": \"ተጋብዘዋል\", \"inviteToRoom\": \"ወደ ክፍል ጋብዝ\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"ፈቃድ መተግበሪያው ማይክሮፎኑን ለመድረስ እየሞከረ ካደ (እርስዎ የአሳሽ ቅንብሮች ወደ ሂድ እና ገጹን ዳግም መጫን ሊኖርብዎ ይችላል)\",\n      \"dismiss\": \"አሰናብት\",\n      \"tryAgain\": \"እንደገና ሞክር\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"ቁልፍ አዘጋጅ\",\n      \"listening\": \"በማዳመጥ ላይ\",\n      \"toggleMuteKeybind\": \"ድምጸ ማጥፍያ/ማብርያ ቁልፍ\",\n      \"togglePushToTalkKeybind\": \"ተጭኖ ማውሪያ ማጥፍያ/ማብርያ ቁልፍ\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"በሆነ ምክንያት ምንም ድምጽ ተጠቃሚ የለም\" },\n    \"addToCalendar\": { \"add\": \"ወደ ቀን መቁጠሪያ ጨምር\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket በአገልጋዩ ተቋረጠ. ሌላ ትር ውስጥ ያለውን ድረ ገጽ በመክፈት የተከሰተ ይሆናል፡፡\",\n      \"reconnect\": \"መልሰህ ተገናኝ\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"ለሁሉም ክፍት\",\n        \"private\": \"የግል\",\n        \"roomName\": \"የክፍል ስም\",\n        \"roomDescription\": \"ክፍል መግለጫ\",\n        \"descriptionError\": \"ከፍተኛ ርዝመት 500\",\n        \"nameError\": \"ከ 2 እስክ 60 ፊደላአት ረዝመት ምሆን አለበት\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"አዲስ ክፍል ተፈጥሯል\",\n        \"roomInviteFrom\": \"ወደ ክፍል ተጋብዘሃል በ \",\n        \"justStarted\": \"ገና አሁን ነው የጀምሩት\",\n        \"likeToJoin\": \", እርሶ ለመቀላቀል ይፈልጋሉ?\",\n        \"inviteReceived\": \"እርስዎ ተጋብዘዋል\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"የተጠቃሚ ስም ወስዶታል\",\n        \"avatarUrlError\": \"ልክ ያልሆነ ምስል\",\n        \"avatarUrlLabel\": \"Github / Twitter አምሳያ ሊንክ\",\n        \"displayNameError\": \"ርዝምት ክ 2 እስከ 50 ፊደላት\",\n        \"displayNameLabel\": \"መጠሪያው ስም\",\n        \"usernameError\": \"ርዝምት ክ 4 እስከ 15 ፊደላት\",\n        \"usernameLabel\": \"የተጠቃሚ ስም\",\n        \"bioError\": \"ከ 160 ፊደል በላይ አይቻልም\",\n        \"bioLabel\": \"ስል እርሶ አጭር ገላጭ ጽሁፍ\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"እርስዎ መቼም ከሚፈጥሩት ማንኛውም ክፍል ከመቀላቀል ይህን ተጠቃሚ ማገድ ይፈልጋሉ፤ እርግጠኛ ኖት?\",\n        \"blockUser\": \"ተጠቃሚን አግድ\",\n        \"makeMod\": \"አወያይ ያድርጉ \",\n        \"unmod\": \"አወያይ አስወግድ\",\n        \"addAsSpeaker\": \"እንደ ተናጋሪ ጨምር\",\n        \"moveToListener\": \"ወደ አድማጭ ቀይር\",\n        \"banFromChat\": \"ከ ውይይት አግድ\",\n        \"banFromRoom\": \"ከ ክፍል ያግዱ\",\n        \"goBackToListener\": \"አዳማጭ ይመለሱ\",\n        \"deleteMessage\": \"ይህን መልዕክት ሰርዝ\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"መናገር ፈቃድ ይጠይቃል\",\n        \"makePublic\": \"ክፍሉን ለሁሉም ክፍት ያድርጉ\",\n        \"makePrivate\": \"ክፍሉን የግል ያድርጉ\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"የተያዘለት ክፍሎች\",\n      \"noneFound\": \"ምንም አልተገኘም\",\n      \"allRooms\": \"ሁሉንም መርሐግብር ክፍሎች\",\n      \"myRooms\": \"የእኔ መርሐግብር ክፍሎች\",\n      \"scheduleRoomHeader\": \"ፕሮግራም ክፍል\",\n      \"startRoom\": \"ክፍሉን ጀምር\",\n      \"modal\": {\n        \"needsFuture\": \"ፍላጎቶች ወደፊት መሆን አለባቸው\",\n        \"roomName\": \"የክፍል ስም\",\n        \"minLength\": \"ደቂቃ ርዝመት 2\",\n        \"roomDescription\": \"ክፍል መግለጫ\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"ውይይት\",\n      \"emotesSoon\": \"[በቅርቡ emotes]\",\n      \"bannedAlert\": \"ከ ውይይቱ ታግደሃል\",\n      \"waitAlert\": \"ሌላ መልዕክት ከመላክ በፊት ትንሽ ጠብቅ\",\n      \"search\": \"ፈልግ\",\n      \"searchResults\": \"የፍለጋ ውጤቶች\",\n      \"recent\": \"በተደጋጋሚ ጥቅም ላይ የዋለ\",\n      \"sendMessage\": \"መልዕክት ይላኩ\",\n      \"whisper\": \"ተንሸካሾክ\",\n      \"welcomeMessage\": \"ለውይይት እንኳን ደህና መጡ!\",\n      \"roomDescription\": \"ክፍል መግለጫ\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ar/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"حمل المزيد\",\n    \"loading\": \"جاري التحميل...\",\n    \"noUsersFound\": \"لم يتم العثور على مستخدمين\",\n    \"ok\": \"حسنا\",\n    \"yes\": \"نعم\",\n    \"no\": \"لا\",\n    \"cancel\": \"إلغاء\",\n    \"save\": \"حفظ\",\n    \"edit\": \"تعديل\",\n    \"delete\": \"حذف\",\n    \"joinRoom\": \"انضم إلى الغرفة\",\n    \"copyLink\": \"انسخ الرابط\",\n    \"copied\": \"تم النسخ\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please give DogeHouse Accessibility permessions\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"مايكروفون مكتوم | DogeHouse\",\n    \"deafenedTitle\": \"مكتوم | DogeHouse\",\n    \"dashboard\": \"لوحة الاعدادات\",\n    \"connectionTaken\": \"اتصال مأخوذ\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"القصة الاصلية\",\n    \"link_2\": \"ديسكورد\",\n    \"link_3\": \"ابلغ عن خلل\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"حظر\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"قائمة المستخدمين غير المتواجدين في غرفة خاصة وأنت تتابعهم.\",\n      \"currentRoom\": \"حاليا في:\",\n      \"startPrivateRoom\": \"ابدأ غرفة خاصة معهم\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"تابع\",\n      \"followingHim\": \"متابع\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"ابدأ غرفة\",\n      \"refresh\": \"تحديث\",\n      \"editRoom\": \"عدل الغرفة\",\n      \"desktopAlert\": \"!على الدسكتوب اليوم DogeHouse حمل تطبيق\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"انتهت الغرفة، عد إلى الوراء\",\n      \"shareRoomLink\": \"شارك رابط الغرفة\",\n      \"inviteFollowers\": \"يمكنك أن تدعو متابعيك المتصلين:\",\n      \"whenFollowersOnline\": \"سيظهر متابعوك هنا عندما يكونوا متصلين.\"\n    },\n    \"login\": {\n      \"headerText\": \"اصطحاب المحادثات الصوتية إلى القمر 🚀\",\n      \"featureText_1\": \"المظهر الداكن\",\n      \"featureText_2\": \"تسجيل مفتوح للجميع\",\n      \"featureText_3\": \"دعم منصات متعددة\",\n      \"featureText_4\": \"مفتوح المصدر\",\n      \"featureText_5\": \"محادثات نصية\",\n      \"featureText_6\": \"Doge تم التطوير بواسطة\",\n      \"loginGithub\": \"GitHub التسجيل باستخدام\",\n      \"loginTwitter\": \"التسجيل باستخدام حساب تويتر\",\n      \"createTestUser\": \"إنشاء حساب تجريبي\",\n      \"loginDiscord\": \"Discord سجل الدخول ب\"\n    },\n    \"myProfile\": {\n      \"logout\": \"تسجيل خروج\",\n      \"probablyLoading\": \"ربما جاري التحميل...\",\n      \"voiceSettings\": \"اذهب إلى إعدادات المحادثة الصوتية \",\n      \"soundSettings\": \"اذهب إلى إعدادات الصوت\",\n      \"deleteAccount\": \"حذف الحساب\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"عفوا، لم نعثر على هذا المستخدم\",\n      \"privacySettings\": \"إعدادات الخصوصية\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"عذرا! ضاعت هذه الصفحة بين المحادثات.\",\n      \"goHomeMessage\": \"لا داعي للقلق، يمكنك الذهاب\",\n      \"goHomeLinkText\": \"الذهاب إلى الصفحة الرئيسة\"\n    },\n    \"room\": {\n      \"speakers\": \"المتحدثون\",\n      \"requestingToSpeak\": \"جاري طلب التحدث\",\n      \"listeners\": \"المستمعون\",\n      \"allowAll\": \"السماح للكل\",\n      \"allowAllConfirm\": \"هل انت متأكد ؟ هذا سيسمح لـ {{count}} مستخدم بالتحدث\"\n    },\n    \"searchUser\": { \"search\": \"ابحث...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"الأصوات\",\n      \"title\": \"اعدادات الصوت\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"تعديل الحساب\",\n      \"followsYou\": \"يتابعك\",\n      \"followers\": \"المتابِعون\",\n      \"following\": \"المتابَعون\",\n      \"followHim\": \"تابع\",\n      \"followingHim\": \"متابَع\",\n      \"copyProfileUrl\": \"انسخ رابط البروفايل\",\n      \"urlCopied\": \"تم نسخ الرابط\",\n      \"unfollow\": \"الغاء المتابعة\",\n      \"about\": \"حول\",\n      \"bot\": \"بوت\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"إعدادات المحادثة الصوتية\",\n      \"mic\": \"ميكروفون:\",\n      \"permissionError\": \"لم يتم العثور على ميكروفونات ، إما أنك لم تقم بتوصيل أي ميكروفون أو لم تمنحنا إذن الوصول.\",\n      \"refresh\": \"تحديث قائمة الميكروفونات\",\n      \"volume\": \"ارتفاع الصوت:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"خطأ في اسم التطبيق\", \"label\": \"ادخل اسم التطبيق\" },\n      \"header\": \"Overlay اعدادات\",\n      \"errorMsg\": \"ادخل اسم تطبيق صحيح\",\n      \"label\": \"ادخل اسم التطبيق\"\n    },\n    \"download\": {\n      \"starting\": \"...بدأ التحميل\",\n      \"failed\": \"لم يتم التنزيل التلقائي الرجاء المحاولة لاحقاً\",\n      \"visit_gh\": \"تحقق من اخر الاصدارات علي github\",\n      \"prompt\": \"أنقر للتنزيل\",\n      \"download_now\": \"تنزيل الآن\",\n      \"download_for\": \"تنزيل إلى %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"اعدادات الخصوصية\",\n      \"header\": \"اعدادات الخصوصية\",\n      \"whispers\": { \"label\": \"الهمسات\", \"on\": \"تشغيل\", \"off\": \"ايقاف\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"روبوتاتك\",\n      \"bots\": \"الروبوتات\",\n      \"title\": \"معلومات البوت\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"اعادة الانشاء\",\n      \"reveal\": \"اضغط لاظهار مقتاح ال api\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"المستخدمون المحظورون\",\n      \"unban\": \"الغاء الحظر\",\n      \"noBans\": \"لم يتم حظر أحد\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"مغادرة الغرفة الحالية\",\n      \"confirmLeaveRoom\": \"هل أنت متأكد من أنك تريد المغادرة؟\",\n      \"leave\": \"غادر\",\n      \"inviteUsersToRoomBtn\": \"دعوة مستخدمين إلى الغرفة\",\n      \"invite\": \"دعوة\",\n      \"toggleMuteMicBtn\": \"كتم الميكروفون و الغاء الكتم\",\n      \"mute\": \"كتم\",\n      \"unmute\": \"الغاء الكتم\",\n      \"makeRoomPublicBtn\": \"اجعل الغرفة عامة!\",\n      \"settings\": \"الإعدادات\",\n      \"speaker\": \"متحدث\",\n      \"listener\": \"مستمع\",\n      \"chat\": \"المحادثة\",\n      \"toggleDeafMicBtn\": \"بدل الكمت\",\n      \"deafen\": \"اكمت\",\n      \"undeafen\": \"لا تكمت\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"جهازك غير مدعوم حاليا. يمكنك إنشاء\",\n      \"linkText\": \"GitHub مشكلة على\",\n      \"addSupport\": \"و سوف أحاول أن أدعم جهازك!\"\n    },\n    \"inviteButton\": { \"invited\": \"مدعو\", \"inviteToRoom\": \"إدعو إلى الغرفة\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"تم رفض الإذن عند محاولة الوصول إلى الميكروفون الخاص بك (قد تحتاج إلى الدخول إلى إعدادات المتصفح وإعادة تحميل الصفحة)\",\n      \"dismiss\": \"رفض\",\n      \"tryAgain\": \"حاول مجدداً\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"ضبط ربط المفتاح\",\n      \"listening\": \"جاري الإستماع\",\n      \"toggleMuteKeybind\": \"تبديل رابط مفتاح كتم الصوت\",\n      \"togglePushToTalkKeybind\": \"تبديل رابط مفتاح الضغط والتحدث\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"لا يوجد مستهلك صوتي لسبب ما\" },\n    \"addToCalendar\": { \"add\": \"أضف إلى التقويم\" },\n    \"wsKilled\": {\n      \"description\": \"تمت مقاطعة الإتصال بواسطة الخادم. يحدث هذا عادةً عند فتح موقع الويب في علامة تبويب أخرى.\",\n      \"reconnect\": \"أعد الاتصال\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"عام\",\n        \"private\": \"خاص\",\n        \"roomName\": \"اسم الغرفة\",\n        \"roomDescription\": \"وصف عن الغرفة\",\n        \"descriptionError\": \"أقصى حد 500\",\n        \"nameError\": \"يجب أن يكون ما بين حرفين و 60 حرف\",\n        \"subtitle\": \"اكمل المعلومات التالية لتبدأ غرفة جديدة\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"تم إنشاء غرفة جديدة\",\n        \"roomInviteFrom\": \"الدعوة من\",\n        \"justStarted\": \"لقد بدؤوا للتو\",\n        \"likeToJoin\": \"، هل تريد الإنضمام؟\",\n        \"inviteReceived\": \"لقد تم دعوتك\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"اسم الحساب مستخدم\",\n        \"avatarUrlError\": \"صورة غير صالحة\",\n        \"avatarUrlLabel\": \"رابط صورة العرض من تويتر/كيت هب\",\n        \"displayNameError\": \"الطول من حرفين إلى 50 حرف\",\n        \"displayNameLabel\": \"عرض الاسم\",\n        \"usernameError\": \"الطول من 4 إلى 15 حرفًا وفقط أبجدي او رقمي او (رمز _)\",\n        \"usernameLabel\": \"اسم المستخدم\",\n        \"bioError\": \"أقصى طول 160 حرفًا\",\n        \"bioLabel\": \"الوصف\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"هل أنت متأكد أنك تريد حظر هذا المستخدم من الانضمام إلى أي غرفة تقوم بإنشائها مستقبلاً؟\",\n        \"blockUser\": \"حجب المستخدم\",\n        \"makeMod\": \"اصنع مود\",\n        \"unmod\": \"إزالة المود\",\n        \"addAsSpeaker\": \"أصف كمتحدث\",\n        \"moveToListener\": \"انقل إلى مستمع\",\n        \"banFromChat\": \"حظر من المحادثة\",\n        \"banFromRoom\": \"حظر من الغرفة\",\n        \"goBackToListener\": \"إرجع إلى مستمع\",\n        \"deleteMessage\": \"حذف الرسالة\",\n        \"makeRoomCreator\": \"اضف كإداري للغرفة\",\n        \"unBanFromChat\": \"الغاء الحظر من المحادثات\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"جعل التحدث بطلب فقط\",\n        \"makePublic\": \"جعل الغرفة عامة\",\n        \"makePrivate\": \"جعل الغرفة خاصة\",\n        \"renamePublic\": \"اختار اسم الغرفة العامة\",\n        \"renamePrivate\": \"اختار اسم الغرفة الخاصة\",\n        \"chatDisabled\": \"تعطيل المحادثة\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"أشخاص\",\n      \"online\": \"اونلاين\",\n      \"noOnline\": \"لديك 0 اصدقاء اونلاين الان\",\n      \"showMore\": \"اظهر المزيد\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"غرف قادمة\",\n      \"exploreMoreRooms\": \"اكتشف غرف اكثر\"\n    },\n    \"search\": {\n      \"placeholder\": \"ابحث عن غرف، اشخاص او تصنيفات\",\n      \"placeholderShort\": \"بحث\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"الحساب الشخصي\",\n      \"language\": \"اللغة\",\n      \"reportABug\": \"ابلغ عن مشكلة\",\n      \"useOldVersion\": \"استعمل نسخة اقدم\",\n      \"logOut\": {\n        \"button\": \"تسجيل الخروج\",\n        \"modalSubtitle\": \"هل انت متأكد انك تريد تسجيل خروجك؟\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"تنزيل التطبيق\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"الغرف المجدولة\",\n      \"noneFound\": \"لا يوجد\",\n      \"allRooms\": \"كل غرفي المجدولة\",\n      \"myRooms\": \"غرفي المجدولة\",\n      \"scheduleRoomHeader\": \"جدوِل غرفة\",\n      \"startRoom\": \"ابدأ غرفة\",\n      \"modal\": {\n        \"needsFuture\": \"يجب أن تكون في المستقبل\",\n        \"roomName\": \"اسم الغرفة\",\n        \"minLength\": \"أقل طول 2\",\n        \"roomDescription\": \"الوصف\"\n      },\n      \"tommorow\": \"غدا\",\n      \"today\": \"اليوم\",\n      \"deleteModal\": {\n        \"areYouSure\": \"هل انت متأكد بأنك تريد حذف هذه الغرفة المقررة؟\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"محادثة\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"لقد تم حظرك من المحادثة\",\n      \"waitAlert\": \"يجب عليك الإنتظار لمدة ثانية قبل إرسال رسالة أخرى\",\n      \"search\": \"ابحث\",\n      \"searchResults\": \"نتائج البحث\",\n      \"recent\": \"مستخدم مؤخرا\",\n      \"sendMessage\": \"أرسل رسالة\",\n      \"whisper\": \"إرسل رسالة خاصة\",\n      \"welcomeMessage\": \"مرحبا بك في المحادثة!\",\n      \"roomDescription\": \"وصف الغرفة\",\n      \"disabled\": \"تم تعطيل المحادثة\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"عبي الصاروخ بنزين\",\n      \"takingOff\": \"تقلع\",\n      \"inSpace\": \"بالفضاء\",\n      \"approachingMoon\": \"الاقتراب من القمر\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"الاقتراب من الشمس\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"الاقتراب من المجره\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"اكتشاف كوكب فيه حياه\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/az/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Daha çox yüklə\",\n    \"loading\": \"Yüklənir...\",\n    \"noUsersFound\": \"İstifadəçi tapılmadı\",\n    \"ok\": \"ОК\",\n    \"yes\": \"Bəli\",\n    \"no\": \"Xeyr\",\n    \"cancel\": \"Ləğv et\",\n    \"save\": \"Yadda saxla\",\n    \"edit\": \"Düzəliş et\",\n    \"delete\": \"Sil\",\n    \"joinRoom\": \"Otağa qoşul\",\n    \"copyLink\": \"Linki kopyala\",\n    \"copied\": \"Kopyalandı\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Zəhmət olmasa DogeHouse-a lazımlı icazələri verin\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Səssiz | DogeHouse\",\n    \"deafenedTitle\": \"Karlaşdırılmış | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Bağlantı alındı\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Tarix\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Səhv haqqında məlumat ver\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Gizli otaqda olmayan və sizin izlədiyiniz istifadəçilər\",\n      \"currentRoom\": \"Hazırda:\",\n      \"startPrivateRoom\": \"Gizli otaq yaradın\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"İzlə\",\n      \"followingHim\": \"İzlənir\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Otaq yaratmaq\",\n      \"refresh\": \"Yenilə\",\n      \"editRoom\": \"Otaqda düzəliş et\",\n      \"desktopAlert\": \"DogeHouse tətbiqini yükləyin!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Otaq bağlandı, geri qayıt\",\n      \"shareRoomLink\": \"Otaq keçidini paylaş\",\n      \"inviteFollowers\": \"Aktiv olan izləyicilərinə dəvət göndər:\",\n      \"whenFollowersOnline\": \"İzləyicilərin aktiv olduqları zaman burada görsənəcdirlər.\"\n    },\n    \"login\": {\n      \"headerText\": \"Səs rabitəsini aya qaldırırığ 🚀\",\n      \"featureText_1\": \"Gecə modu\",\n      \"featureText_2\": \"Açıq qeydiyyat\",\n      \"featureText_3\": \"Cross-Platform Dəstək\",\n      \"featureText_4\": \"Açıq qaynaq\",\n      \"featureText_5\": \"Çat\",\n      \"featureText_6\": \"Doge sayəsində işləyir\",\n      \"loginGithub\": \"GitHub ilə daxil ol\",\n      \"loginTwitter\": \"Twitter ilə daxil ol\",\n      \"createTestUser\": \"Test istifadəçini yarat\",\n      \"loginDiscord\": \"Discord ilə daxil ol\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Çıxış\",\n      \"probablyLoading\": \"Yüklənir...\",\n      \"voiceSettings\": \"Mikrofon parametrləri\",\n      \"soundSettings\": \"Səs parametrləri\",\n      \"deleteAccount\": \"Hesabı sil\",\n      \"overlaySettings\": \"Overlay parametrləri\",\n      \"couldNotFindUser\": \"Təəssüf, istifadəçi tapılmadı\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Bu səhifə tapılmadı.\",\n      \"goHomeMessage\": \"Narahat olmayın. Siz uğur qazanacaqsınız\",\n      \"goHomeLinkText\": \"Əsas səhifəyə keç\"\n    },\n    \"room\": {\n      \"speakers\": \"Danışanlar\",\n      \"requestingToSpeak\": \"Danışmaq istəyənlər\",\n      \"listeners\": \"Dinləyicilər\",\n      \"allowAll\": \"Hərkəsə icazə ver\",\n      \"allowAllConfirm\": \"Təsdiqləyirsiniz? Bu seçim {{count}} istifadəçiyə danışma haqqı verəcəkdir\"\n    },\n    \"searchUser\": { \"search\": \"Axtarış...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Səslər\",\n      \"title\": \"Səs paratmertləri\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profildə düzəliş et\",\n      \"followsYou\": \"Sizi izləyənlər\",\n      \"followers\": \"İzləyicilər\",\n      \"following\": \"İzləyirsiniz\",\n      \"followHim\": \"İzlə\",\n      \"followingHim\": \"İzləyirsiniz\",\n      \"copyProfileUrl\": \"Profil keçidini kopyalayın\",\n      \"urlCopied\": \"Keçid kopyalandı\",\n      \"unfollow\": \"İzləməni dayandır\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Səs parametrləri\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Mikrofon tapılmadı, ya mikrofonu taxmamısınız ya da sayt üçün lazımlı icazəni verməmisiniz.\",\n      \"refresh\": \"Yenilə\",\n      \"volume\": \"Səs:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Uyğunsuz tətbiq adı\",\n        \"label\": \"Tətbiq adını daxil et\"\n      },\n      \"header\": \"Overlay parametrləri\",\n      \"errorMsg\": \"Zəhmət olmasa uyğun bir tətbiq adı daxil edin\",\n      \"label\": \"Tətbiq başlığını daxil et\"\n    },\n    \"download\": {\n      \"starting\": \"Yükləmə başlanır...\",\n      \"failed\": \"Avtomatik yükəmədə problem aşkarlandı, zəhmət olmasa daha sonra bir daha yoxlayın\",\n      \"visit_gh\": \"Github çıxışlarını ziyarət et\",\n      \"prompt\": \"Aşağıdakı düyməyə basaraq yükləməni başladın\",\n      \"download_now\": \"İndi yüklə\",\n      \"download_for\": \"{{platform}} üçün yüklə\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Banlanmış istifadəçilər\",\n      \"unban\": \"Banı ləğv et\",\n      \"noBans\": \"Banlanan istifadəçi yoxdur\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Hazırkı otağı tərk et\",\n      \"confirmLeaveRoom\": \"Tərk etmək istədiyinizə əminsiniz?\",\n      \"leave\": \"Tərk et\",\n      \"inviteUsersToRoomBtn\": \"İstifadəçiləri otağa dəvət et\",\n      \"invite\": \"Dəvət et\",\n      \"toggleMuteMicBtn\": \"Mikrofonu aç/bağla\",\n      \"mute\": \"Səssizə keçir\",\n      \"unmute\": \"Səsliyə keçir\",\n      \"makeRoomPublicBtn\": \"Otağı hərkəsə açıq et!\",\n      \"settings\": \"Parametlər\",\n      \"speaker\": \"Danışanlar\",\n      \"listener\": \"Dinləyicilər\",\n      \"chat\": \"Çat\",\n      \"toggleDeafMicBtn\": \"Karlaşdırılmış modu aç/bağla\",\n      \"deafen\": \"Karlaşdır\",\n      \"undeafen\": \"Karlaşdırmanı ləğv et\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Sizin cihaz dəstəklənmir. Siz yarada bilərsiz\",\n      \"linkText\": \"GitHub-da sorğu\",\n      \"addSupport\": \"və mən sizin cihaz üçün dəstək əlavə etməyə çalışacağam!\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Dəvət olunub\",\n      \"inviteToRoom\": \"Otağa dəvət et\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Mikrofona keçid rədd edildi (Siz browser parametrlərinə keçid edib səhifəni yeniləməlisiniz)\",\n      \"dismiss\": \"Rədd et\",\n      \"tryAgain\": \"Yenidən cəhd et\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Düymə kombinasiası quraşdır\",\n      \"listening\": \"Qulaq asılır\",\n      \"toggleMuteKeybind\": \"Susdurma açma/bağlama düymə kombinasiyası\",\n      \"togglePushToTalkKeybind\": \"Bas danış açma/bağlama düymə kombinasiyası\",\n      \"toggleOverlayKeybind\": \"Overlay açma/bağlama düymə kombinasiyası\",\n      \"toggleDeafKeybind\": \"Karlaşdırma açma/bağlama düymə kombinasiyası\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Bir səbəbə görə səs qəbuledici yoxdur!\"\n    },\n    \"addToCalendar\": { \"add\": \"Kalendara əlavə et\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket server tərəfindən bağlandı. Bu adətən birdən çox səhifədə saytı açdığınız zaman baş verir.\",\n      \"reconnect\": \"Yenidən bağlantı qur\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Hərkəsə açıq\",\n        \"private\": \"Gizli\",\n        \"roomName\": \"Otaq adı\",\n        \"roomDescription\": \"Otaq haqqında\",\n        \"descriptionError\": \"maksimum uzunluq 500\",\n        \"nameError\": \"2 və 60 simvol uzunluğunda olmalıdır\",\n        \"subtitle\": \"Boşluqları dolduraraq yeni otaq başladın\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Yeni otaq yaradıldı\",\n        \"roomInviteFrom\": \"Otaq dəvəti: \",\n        \"justStarted\": \"Onlar daha yeni başladılar\",\n        \"likeToJoin\": \", qoşulmaq istəyirsiniz?\",\n        \"inviteReceived\": \"Siz dəvət edildiniz: \"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"İstifadəçi adı götürülüb\",\n        \"avatarUrlError\": \"Uyğun olmayan şəkil\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar keçidi\",\n        \"displayNameError\": \"uzunluq 2 və 50 simvol arasında\",\n        \"displayNameLabel\": \"Görsənən ad\",\n        \"usernameError\": \"uzunluq 4 və 15 simvol uzunluğunda və sadəcə alfanumerik/alt xətt simvollarından ibarət olmalıdır.\",\n        \"usernameLabel\": \"İstifadəçi adı\",\n        \"bioError\": \"maksimum uzunluq 160 simvoldur\",\n        \"bioLabel\": \"Bioqrafiya\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Siz bu istifadəçini yaratdığınız bütün otaqlara qoşulmaması üçün bloklamağa əminsinizmi?\",\n        \"blockUser\": \"İstifəçini blokla\",\n        \"makeMod\": \"Mod et\",\n        \"unmod\": \"Modluğu ləğv et\",\n        \"addAsSpeaker\": \"Danışan kimi əlavə et\",\n        \"moveToListener\": \"Dinləyiciyə keçir\",\n        \"banFromChat\": \"Çatdan banla\",\n        \"banFromRoom\": \"Otaqdan banla\",\n        \"goBackToListener\": \"Dinləyiciyə geri qayıt\",\n        \"deleteMessage\": \"Bu mesajı sil\",\n        \"makeRoomCreator\": \"Otaq admini et\",\n        \"unBanFromChat\": \"Çatdan banı ləğv et\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Danışmaq üçün icazə istə\",\n        \"makePublic\": \"Otağı hərkəsə açıq et\",\n        \"makePrivate\": \"Otağı gizli et\",\n        \"renamePublic\": \"Hərkəsə açıq otaq adı quraşdır\",\n        \"renamePrivate\": \"Gizli otaq adı quraşdır\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"İnsanlar\",\n      \"online\": \"Aktiv\",\n      \"noOnline\": \"Sizin hazırda 0 aktiv dostunuz var\",\n      \"showMore\": \"Daha çox göstər\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Növbəti otaqlar\",\n      \"exploreMoreRooms\": \"Daha çox otaq kəşf et\"\n    },\n    \"search\": {\n      \"placeholder\": \"Otaqlar, istifadəçilər və ya kateqoriyalar axtar\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Dil\",\n      \"reportABug\": \"Səhv bildir\",\n      \"useOldVersion\": \"Əvvəlki versiyanı istifadə et\",\n      \"logOut\": {\n        \"button\": \"Çıxış\",\n        \"modalSubtitle\": \"Çıxmaq istədiyinizə əminsiniz?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Səsi incələ\",\n        \"stopDebugger\": \"İncələmən dayandır\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Planlaşdırılmış otaqlar\",\n      \"noneFound\": \"Tapılmadı\",\n      \"allRooms\": \"Bütün planlaşdırılmış otaqlar\",\n      \"myRooms\": \"Mənim planlaşdırılmış otaqlarım\",\n      \"scheduleRoomHeader\": \"Otaq planlaşdır\",\n      \"startRoom\": \"Otaq başlat\",\n      \"modal\": {\n        \"needsFuture\": \"Gələcək zamanda olmalıdır.\",\n        \"roomName\": \"Otaq adı\",\n        \"minLength\": \"Minimum uzunluq 2\",\n        \"roomDescription\": \"Haqqında\"\n      },\n      \"tommorow\": \"Sabah\",\n      \"today\": \"Bu gün\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Bu planlanmış otağı silmək istədiyinizə əminsiniz?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Çat\",\n      \"emotesSoon\": \"[emodzi tezliklə]\",\n      \"bannedAlert\": \"Bu söhbətdə blokdasınız\",\n      \"waitAlert\": \"Siz növbəti mesajı göndərmək üçün bir saniyə gözləməlisiniz.\",\n      \"search\": \"Axtarış\",\n      \"searchResults\": \"Axtarış nəticələri\",\n      \"recent\": \"Tez-tez istifadə olunan\",\n      \"sendMessage\": \"Mesaj göndər\",\n      \"whisper\": \"Pıçıltı\",\n      \"welcomeMessage\": \"Çata xoş gəlmisiniz!\",\n      \"roomDescription\": \"Otaq haqqında\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Roket yanacağı doldurulur.\",\n      \"takingOff\": \"Uçuş başlanır\",\n      \"inSpace\": \"Havada\",\n      \"approachingMoon\": \"Aya yaxınlaşırıq\",\n      \"lunarDoge\": \"Ay doge-u\",\n      \"approachingSun\": \"Günəşə yaxınlaşırıq\",\n      \"solarDoge\": \"Günəş doge-u\",\n      \"approachingGalaxy\": \"Qalaktikaya yaxınlaşırıq\",\n      \"galacticDoge\": \"Qalaktika dog-u\",\n      \"spottedLife\": \"Həyat olan planet aşkarlandı\"\n    },\n    \"feed\": { \"yourFeed\": \"Sənin lentin\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/bg/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"зареди още\",\n    \"loading\": \"зареждане...\",\n    \"noUsersFound\": \"не са намерени потребители\",\n    \"ok\": \"ок\",\n    \"yes\": \"да\",\n    \"no\": \"не\",\n    \"cancel\": \"прекрати\",\n    \"save\": \"запази\",\n    \"edit\": \"промени\",\n    \"delete\": \"изтрий\",\n    \"joinRoom\": \"влез в стая\",\n    \"copyLink\": \"копирай линк\",\n    \"copied\": \"копирано\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please give DogeHouse Accessibility permessions\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"История\",\n    \"link_2\": \"Дискорд\",\n    \"link_3\": \"Докладвай за неизправност\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"бан\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Списък от потребители, които не са в \",\n      \"currentRoom\": \"в момента в:\",\n      \"startPrivateRoom\": \"start a private room with them\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Създай стая\",\n      \"refresh\": \"Опресни\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"изтекла стая, върни се назад\",\n      \"shareRoomLink\": \"сподели линк към стая\",\n      \"inviteFollowers\": \"Можеш да поканиш последователи, които са на линия:\",\n      \"whenFollowersOnline\": \"Когато последователите ти са на линия, че се появят тук.\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"Тъмна тема\",\n      \"featureText_2\": \"Отвори влизания\",\n      \"featureText_3\": \"Mеждуплатформена Pоддръжка\",\n      \"featureText_4\": \"Отворен Код\",\n      \"featureText_5\": \"Текстов Чат\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"влизане с GitHub\",\n      \"loginTwitter\": \"влизане с Twitter\",\n      \"createTestUser\": \"създаване на тестови потребител\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"излизане\",\n      \"probablyLoading\": \"зареждане най-вероятно...\",\n      \"voiceSettings\": \"отиване към гласови настройки\",\n      \"soundSettings\": \"отиване към звукови настройки\",\n      \"deleteAccount\": \"изтриване на акаунт\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Опаа! Тази страниза бе изгубена.\",\n      \"goHomeMessage\": \"Не се тревожи. Можеш\",\n      \"goHomeLinkText\": \"Към начален екран\"\n    },\n    \"room\": {\n      \"speakers\": \"Говорители\",\n      \"requestingToSpeak\": \"Заявка за говорене\",\n      \"listeners\": \"Слушатели\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"търсене...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Звук\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"промяна на профил\",\n      \"followsYou\": \"следват те\",\n      \"followers\": \"последователи\",\n      \"following\": \"последвани\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Звукови настройки\",\n      \"mic\": \"микрофон:\",\n      \"permissionError\": \"не бяха открити микрофони, най-вероятно няма вързани или нямат разрешение да бъдат ползвани.\",\n      \"refresh\": \"опресняване на списък с микрофони\",\n      \"volume\": \"ниво на звук:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\",\n      \"errorMsg\": \"Please enter valid app title\",\n      \"label\": \"Enter app title\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Баннати потребители\",\n      \"unban\": \"премахване на бан\",\n      \"noBans\": \"все още никой не е баннат\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Излизане от сегашната стая\",\n      \"confirmLeaveRoom\": \"Сигурни ли сте, че искате да излезете?\",\n      \"leave\": \"Излизане\",\n      \"inviteUsersToRoomBtn\": \"Канене на потребители в стаята\",\n      \"invite\": \"Покани\",\n      \"toggleMuteMicBtn\": \"Превключване на заглушаване\",\n      \"mute\": \"Заглушаване\",\n      \"unmute\": \"Отзаглушаване\",\n      \"makeRoomPublicBtn\": \"Направи стаята публична!\",\n      \"settings\": \"Настройки\",\n      \"speaker\": \"Говорител\",\n      \"listener\": \"Слушател\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Вашето устройство не е поддържано. Можете да създадете\",\n      \"linkText\": \"issue в GitHub\",\n      \"addSupport\": \"и аз ще пробвам да добавя поддръжка за вашето устройство.\"\n    },\n    \"inviteButton\": { \"invited\": \"поканен\", \"inviteToRoom\": \"канене в стаята\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Проблем с позволенията в браузъра (може да се наложи да отидете в настройките на браузъра и да презаредите страницата)\",\n      \"dismiss\": \"отхвърляне\",\n      \"tryAgain\": \"повторен опит\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"задай клавишна комбинация\",\n      \"listening\": \"слушане\",\n      \"toggleMuteKeybind\": \"превключване на клавишна комбинация за заглушаване\",\n      \"togglePushToTalkKeybind\": \"превключване на клавишна комбинация за натисни-за-говорене\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"по някаква причина няма звуков консуматор\"\n    },\n    \"addToCalendar\": { \"add\": \"Добави към календар\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket-а беше спрян от сървъра. Това обикновенно се случва, когато сайта е отворен в друг прозорец.\",\n      \"reconnect\": \"повторно свързване\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"публична\",\n        \"private\": \"частна\",\n        \"roomName\": \"има на стая\",\n        \"roomDescription\": \"описание на стаята\",\n        \"descriptionError\": \"максимална дължина 500\",\n        \"nameError\": \"името трябва да е между 2 и 60 знака\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Нова стая беше създадена\",\n        \"roomInviteFrom\": \"Покана от стая\",\n        \"justStarted\": \"Туко-що започнаха\",\n        \"likeToJoin\": \", ще се присъедините ли?\",\n        \"inviteReceived\": \"бяхте поканени в\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"потребителското име е взето\",\n        \"avatarUrlError\": \"Невалидна картинка\",\n        \"avatarUrlLabel\": \"линк към Github/Twitter/Discord аватар\",\n        \"displayNameError\": \"дължина между 2 и 60 знака\",\n        \"displayNameLabel\": \"Показващо се име\",\n        \"usernameError\": \"дължина между 4 и 15, само букви, цифри и долна черта\",\n        \"usernameLabel\": \"Потребителско име\",\n        \"bioError\": \"максимална дължина 160 знака\",\n        \"bioLabel\": \"Описание\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Сигурни ли сте, че искате да блокирате този потребител да влиза във всяка стая, която създавате?\",\n        \"blockUser\": \"блокиране на потребител\",\n        \"makeMod\": \"направи модератор\",\n        \"unmod\": \"премахни модератор\",\n        \"addAsSpeaker\": \"добави като говорител\",\n        \"moveToListener\": \"премести като слушател\",\n        \"banFromChat\": \"блокиране от чат\",\n        \"banFromRoom\": \"блокиране от стая\",\n        \"goBackToListener\": \"върни се към слушател\",\n        \"deleteMessage\": \"изтриване на това съобщение\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"изискване на позволение за говорене\",\n        \"makePublic\": \"направи стаята публична\",\n        \"makePrivate\": \"направи стаята частна\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Планирани стаи\",\n      \"noneFound\": \"няма открити\",\n      \"allRooms\": \"всички планирани стаи\",\n      \"myRooms\": \"моите планирани стаи\",\n      \"scheduleRoomHeader\": \"Планирай стая\",\n      \"startRoom\": \"започни стая\",\n      \"modal\": {\n        \"needsFuture\": \"трябва да е в бъдещето\",\n        \"roomName\": \"име на стаята\",\n        \"minLength\": \"минимално 2 знака\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Чат\",\n      \"emotesSoon\": \"[emotes скоро]\",\n      \"bannedAlert\": \"Бяхте блокирани от чата\",\n      \"waitAlert\": \"Трябва да изчакате секунда преди да изпратите съобщение\",\n      \"search\": \"Търси\",\n      \"searchResults\": \"Резултати от търсенето\",\n      \"recent\": \"Често използвани\",\n      \"sendMessage\": \"Изпрати съобщение\",\n      \"whisper\": \"Прошепни\",\n      \"welcomeMessage\": \"Добре дошъл в чата!\",\n      \"roomDescription\": \"описание на стаята\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/bn/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"আরো লোড করুন\",\n    \"loading\": \"লোডিং...\",\n    \"noUsersFound\": \"কোনো ইউজার পাওয়া যায়নি\",\n    \"ok\": \"ওকে\",\n    \"yes\": \"হ্যাঁ\",\n    \"no\": \"না\",\n    \"cancel\": \"বাতিল\",\n    \"save\": \"সেভ\",\n    \"edit\": \"এডিট\",\n    \"delete\": \"মুছুন\",\n    \"joinRoom\": \"রুমে ঢুকুন\",\n    \"copyLink\": \"লিংক কপি করুন\",\n    \"copied\": \"কপি হয়ে গেছে\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"দয়া করে DogeHouse কে অ্যাক্সেস পারমিশন দিন\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"নিঃশব্দ | DogeHouse\",\n    \"deafenedTitle\": \"সাউন্ড বন্ধ | DogeHouse\",\n    \"dashboard\": \"ড্যাশবোর্ড\",\n    \"connectionTaken\": \"কানেকশন নেওয়া হয়ে গিয়েছে\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"অরিজিন স্টোরি\",\n    \"link_2\": \"ডিসকর্ড\",\n    \"link_3\": \"বাগ রিপোর্ট করুন\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ব্যান করুন\",\n      \"userStaffandContrib\": \"ইউজার স্টাফ & অবদান\",\n      \"staff\": \"স্টাফ: \",\n      \"contributions\": \"অবদান\",\n      \"username\": \"ইউজার নাম\",\n      \"usrStaff\": \"ইউজার স্টাফ\",\n      \"usrContributions\": \"ইউজারের অবদানগুলো\",\n      \"reason\": \"কারণ\",\n      \"usernamePlaceholder\": \"ইউজার নাম যার উপর কাজ সম্পাদন করা হবে\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"যেসব ইউজারকে আপনি অনুসরণ করেন কিন্তু এখন তারা কোনো প্রাইভেট রুম এ নেই।\",\n      \"currentRoom\": \"ভেতরে আছেন:\",\n      \"startPrivateRoom\": \"তাদের সাথে প্রাইভেট রুম শুরু করুন\",\n      \"title\": \"মানুষ\"\n    },\n    \"followList\": {\n      \"followHim\": \"অনুসরণ করুন\",\n      \"followingHim\": \"অনুসরণ করছেন\",\n      \"title\": \"মানুষ\",\n      \"followingNone\": \"কাউকে অনুসরণ করছেন না\",\n      \"noFollowers\": \"কোনো অনুসরণকারী নেই\"\n    },\n    \"home\": {\n      \"createRoom\": \"রুম বানান\",\n      \"refresh\": \"রিফ্রেশ করুন\",\n      \"editRoom\": \"রুম এডিট করুন\",\n      \"desktopAlert\": \"ডাউনলোড করুন DogeHouse ডেস্কটপ অ্যাপ্লিকেশন আজকেই!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"রুম শেষ, ফিরে আসুন\",\n      \"shareRoomLink\": \"রুমের লিংক শেয়ার করুন\",\n      \"inviteFollowers\": \"আপনি আপনার অনুসরণকারীদের আমন্ত্রণ করতে পারেন যারা এখন অনলাইনে রয়েছেন:\",\n      \"whenFollowersOnline\": \"যখন আপনার অনুসরণকারীরা অনলাইন হবে, তাদেরকে এখানে দেখাবে।\"\n    },\n    \"login\": {\n      \"headerText\": \"আপনার আওয়াজ চাঁদ পর্যন্ত নিয়ে যান 🚀\",\n      \"featureText_1\": \"ডার্ক থিম\",\n      \"featureText_2\": \"ওপেন সাইন-আপ\",\n      \"featureText_3\": \"ক্রস প্লাটফর্ম সাপোর্ট\",\n      \"featureText_4\": \"ওপেন সোর্স\",\n      \"featureText_5\": \"টেক্সট চ্যাট\",\n      \"featureText_6\": \"সৌজন্যে Doge\",\n      \"loginGithub\": \"GitHub এর দ্বারা লগইন করুন\",\n      \"loginTwitter\": \"Twitter এর দ্বারা লগইন করুন\",\n      \"createTestUser\": \"টেস্ট ইউজার তৈরি করুন\",\n      \"loginDiscord\": \"Discord এর দ্বারা লগইন করুন\"\n    },\n    \"myProfile\": {\n      \"logout\": \"লগআউট\",\n      \"probablyLoading\": \"সম্ভবত লোডিং...\",\n      \"voiceSettings\": \"ভয়েস সেটিংস এ যান\",\n      \"soundSettings\": \"সাউন্ড সেটিংস এ যান\",\n      \"deleteAccount\": \"অ্যাকাউন্ট ডিলিট করুন\",\n      \"overlaySettings\": \"ওভারলে সেটিংস এ যান\",\n      \"couldNotFindUser\": \"দুঃখিত, আমরা সেই ইউজারকে খুঁজে পাইনি\",\n      \"privacySettings\": \"গোপনীয়তা সেটিংস\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"উফ! এই পেজের বার্তালাপ কেটে গেছে।\",\n      \"goHomeMessage\": \"চিন্তা করবেন না। আপনি পারবেন\",\n      \"goHomeLinkText\": \"হোমে যেতে\"\n    },\n    \"room\": {\n      \"speakers\": \"স্পিকারস\",\n      \"requestingToSpeak\": \"কথা বলার জন্য অনুরোধ করছেন\",\n      \"listeners\": \"শ্রোতাগণ\",\n      \"allowAll\": \"সবাইকে অনুমতি দিন\",\n      \"allowAllConfirm\": \"আপনি কি নিশ্চিত? এটা অনুমতি দেবে সব {{count}} জন অনুরোধ করা ইউজারদের কথা বলতে\"\n    },\n    \"searchUser\": { \"search\": \"খুঁজুন...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"সাউন্ডস\",\n      \"title\": \"সাউন্ড সেটিংস\",\n      \"playSound\": \"সাউন্ড প্লে করুন\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"প্রোফাইল এডিট করুন\",\n      \"followsYou\": \"আপনাকে অনুসরণ করে\",\n      \"followers\": \"অনুসরণকারীরা\",\n      \"following\": \"অনুসরণ করছেন\",\n      \"followHim\": \"অনুসরণ করুন\",\n      \"followingHim\": \"অনুসরণ করছেন\",\n      \"copyProfileUrl\": \"প্রোফাইলের URL কপি করুন\",\n      \"urlCopied\": \"URL ক্লিপবোর্ডে কপি করা হয়েছে\",\n      \"unfollow\": \"অনুসরণ বাতিল করুন\",\n      \"about\": \"সম্বন্ধে\",\n      \"bot\": \"বট\",\n      \"profileTabs\": {\n        \"about\": \"সম্বন্ধে\",\n        \"rooms\": \"রুমগুলো\",\n        \"scheduled\": \"পরিকল্পিত\",\n        \"recorded\": \"রেকর্ড করেছেন\",\n        \"clips\": \"ক্লিপগুলো\",\n        \"admin\": \"অ্যাডমিন\"\n      },\n      \"block\": \"ব্লক করুন\",\n      \"unblock\": \"আনব্লক করুন\",\n      \"sendDM\": \"মেসেজ করুন\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"ভয়েস সেটিংস\",\n      \"mic\": \"মাইক:\",\n      \"permissionError\": \"কোনো মাইক খুঁজে পাওয়া যাইনি, আপনি কোনো মাইক প্লাগ করেননি অথবা এই ওয়েবসাইটকে পারমিশন দেননি।\",\n      \"refresh\": \"মাইক লিস্ট রিফ্রেশ করুন\",\n      \"volume\": \"ভলিউম:\",\n      \"title\": \"ভয়েস সেটিংস\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"অবৈধ অ্যাপ শিরোনাম\",\n        \"label\": \"অ্যাপ শিরোনাম দিন\"\n      },\n      \"header\": \"ওভারলে সেটিংস\",\n      \"errorMsg\": \"দয়া করে বৈধ অ্যাপ শিরোনাম দিন\",\n      \"label\": \"অ্যাপ শিরোনাম দিন\"\n    },\n    \"download\": {\n      \"starting\": \"ডাউনলোড শুরু হচ্ছে...\",\n      \"failed\": \"অটো-ডাউনলোড করা যাচ্ছে না, পরে আবার চেষ্টা করুন\",\n      \"visit_gh\": \"যান গিটহাব রিলিজেস এ\",\n      \"prompt\": \"নিচের বাটনটিতে ক্লিক করুন ডাউনলোড শুরু করতে\",\n      \"download_now\": \"ডাউনলোড করুন এখন\",\n      \"download_for\": \"%platform% (%ext%)-এর জন্য ডাউনলোড করুন\"\n    },\n    \"privacySettings\": {\n      \"title\": \"গোপনীয়তা সেটিংস\",\n      \"header\": \"গোপনীয়তা সেটিংস\",\n      \"whispers\": { \"label\": \"ফিসফিস\", \"on\": \"অন\", \"off\": \"অফ\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"ব্যান করা ইউজারস\",\n      \"unban\": \"আনব্যান\",\n      \"noBans\": \"কেও এখনো ব্যান হয়নি\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"বর্তমান রুম থেকে বেরিয়ে যান\",\n      \"confirmLeaveRoom\": \"আপনি কি বেরিয়ে যেতে ইচ্ছুক?\",\n      \"leave\": \"বেরোন\",\n      \"inviteUsersToRoomBtn\": \"রুমে ইউজারদের ডাকুন\",\n      \"invite\": \"ডাকুন\",\n      \"toggleMuteMicBtn\": \"মাইক মিউট টগল করুন\",\n      \"mute\": \"মিউট\",\n      \"unmute\": \"আনমিউট\",\n      \"makeRoomPublicBtn\": \"রুমটাকে পাবলিক করুন!\",\n      \"settings\": \"সেটিংস\",\n      \"speaker\": \"স্পিকার\",\n      \"listener\": \"শ্রোতাগণ\",\n      \"chat\": \"চ্যাট\",\n      \"toggleDeafMicBtn\": \"সাউন্ড টগল করুন\",\n      \"deafen\": \"সাউন্ড বন্ধ\",\n      \"undeafen\": \"সাউন্ড অন\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"আপনার ডিভাইসটি বর্তমানে সাপোর্টেড নয়। আপনি তৈরি করতে পারেন একটি\",\n      \"linkText\": \"ইস্যু GitHub - এ\",\n      \"addSupport\": \"এবং আমি চেষ্টা করব আপনার ডিভাইসের জন্য সাপোর্ট আনার।\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"আমন্ত্রিত\",\n      \"inviteToRoom\": \"রুম এ আমন্ত্রণ করুন\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"পারমিশন পাওয়া যাইনি আপনার মাইকের (আপনি ব্রাউজার সেটিংস এ গিয়ে রিলোড করুন পেজটিকে)\",\n      \"dismiss\": \"বাদ দিন\",\n      \"tryAgain\": \"পুনরায় চেষ্টা করুন\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"কীবাইন্ড সেট করুন\",\n      \"listening\": \"শুনুন\",\n      \"toggleMuteKeybind\": \"টগল করুন মাইক কীবাইন্ড\",\n      \"togglePushToTalkKeybind\": \"টগল করুন পুশ-টু-টক কীবাইন্ড\",\n      \"toggleOverlayKeybind\": \"টগল করুন ওভারলে কীবাইন্ড\",\n      \"toggleDeafKeybind\": \"টগল করুন সাউন্ড কীবাইন্ড\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"কিছু কারনে অডিও কনসিউম করা যাইনি\"\n    },\n    \"addToCalendar\": { \"add\": \"ক্যালেন্ডারে যুক্ত করুন\" },\n    \"wsKilled\": {\n      \"description\": \"সার্ভারের দ্বারা ওয়েবসকেটটি শেষ হয়ে গেছে। এটি সাধারণত হয় যখন আপনি ওয়েবসাইটটি আরেকটি ট্যাব এ ওপেন করেন।\",\n      \"reconnect\": \"পুনরায় সংযোগ করুন\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"পাবলিক\",\n        \"private\": \"প্রাইভেট\",\n        \"roomName\": \"রুমের নাম\",\n        \"roomDescription\": \"রুমের সম্বন্ধে\",\n        \"descriptionError\": \"৫০০ শব্দের মধ্যে হতে হবে\",\n        \"nameError\": \"২ থেকে ৬০ অক্ষরের মধ্যে হতে হবে\",\n        \"subtitle\": \"নিচের স্থানগুলো পূরণ করুন একটি নতুন রুম শুরু করতে\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"নতুন রুম তৈরি হয়েছে\",\n        \"roomInviteFrom\": \"রুমের আমন্ত্রণ যার থেকে\",\n        \"justStarted\": \"তারা শুরু করেছে\",\n        \"likeToJoin\": \", আপনি কি যুক্ত হতে চান?\",\n        \"inviteReceived\": \"আপনাকে আমন্ত্রণ করা হয়েছে\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"ইউজার নাম নেওয়া হয়ে গিয়েছে\",\n        \"avatarUrlError\": \"অবৈধ ছবি\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord এর Avatar URL\",\n        \"displayNameError\": \"২ থেকে ৬০ অক্ষরের মধ্যে হতে হবে\",\n        \"displayNameLabel\": \"ডিসপ্লে নাম\",\n        \"usernameError\": \"২ থেকে ৬০ অক্ষরের মধ্যে হতে হবে এবং শুধুমাত্র বর্ণমালা ও সংখ্যা/আন্ডারস্কোর\",\n        \"usernameLabel\": \"ইউজার নাম\",\n        \"bioError\": \"১৬০ অক্ষরের মধ্যে হতে হবে\",\n        \"bioLabel\": \"বর্ণনা\",\n        \"bannerUrlLabel\": \"টুইটার ব্যানার URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"আপনি কি নিশ্চিত যে আপনি এই ইউজারকে চিরকালের জন্য ব্লক করছেন আপনার সব রুমে জয়েন করা থেকে?\",\n        \"blockUser\": \"ইউজার ব্লক করুন\",\n        \"makeMod\": \"মডারেটর তৈরি করুন\",\n        \"unmod\": \"মডারেটর থেকে রিমুভ করুন\",\n        \"addAsSpeaker\": \"স্পিকার হিসেবে যুক্ত করুন\",\n        \"moveToListener\": \"শ্রোতাতে পাঠান\",\n        \"banFromChat\": \"চ্যাট থেকে ব্যান করুন\",\n        \"banFromRoom\": \"রুম থেকে ব্যান করুন\",\n        \"goBackToListener\": \"শ্রোতাতে ফিরে যান\",\n        \"deleteMessage\": \"মেসেজটি মুছে ফেলুন\",\n        \"makeRoomCreator\": \"রুম অ্যাডমিন তৈরি করুন\",\n        \"unBanFromChat\": \"চ্যাট থেকে আনব্যান করুন\",\n        \"banIPFromRoom\": \"রুম থেকে IP ব্যান করুন\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"কথা বলার জন্য পারমিশন লাগবে\",\n        \"makePublic\": \"রুমটাকে পাবলিক করুন\",\n        \"makePrivate\": \"রুমটাকে প্রাইভেট করুন\",\n        \"renamePublic\": \"পাবলিক রুমের নাম সেট করুন\",\n        \"renamePrivate\": \"প্রাইভেট রুমের নাম সেট করুন\",\n        \"chatDisabled\": \"চ্যাট অফ করুন\",\n        \"chatCooldown\": \"চ্যাট কুলডাউন (মিলিসেকেন্ড)\",\n        \"chat\": {\n          \"label\": \"চ্যাট\",\n          \"enabled\": \"অন করা\",\n          \"disabled\": \"অফ করা\",\n          \"followerOnly\": \"অনুসরণকারীদের জন্য শুধু\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"মানুষ\",\n      \"online\": \"অনলাইন\",\n      \"noOnline\": \"এই মুহুর্তে আপনার অনলাইনে ০ জন বন্ধু রয়েছে\",\n      \"showMore\": \"আরো দেখুন\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"আসন্ন রুমগুলো\",\n      \"exploreMoreRooms\": \"আরো রুম এক্সপ্লোর করুন\"\n    },\n    \"search\": {\n      \"placeholder\": \"সার্চ করুন রুম, ইউজার অথবা ক্যাটেগরি\",\n      \"placeholderShort\": \"সার্চ করুন\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"প্রোফাইল\",\n      \"language\": \"ভাষা\",\n      \"reportABug\": \"বাগ রিপোর্ট করুন\",\n      \"useOldVersion\": \"পুরাতন ভার্সন ব্যবহার করুন\",\n      \"logOut\": {\n        \"button\": \"লগ আউট\",\n        \"modalSubtitle\": \"আপনি কি নিশ্চিত আপনি লগ আউট করতে চান?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"অডিও ডিবাগ করুন\",\n        \"stopDebugger\": \"ডিবাগার বন্ধ করুন\"\n      },\n      \"downloadApp\": \"ডাউনলোড করুন অ্যাপ্লিকেশনটি\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse স্টাফ\",\n      \"dhContributor\": \"DogeHouse অবদানকারী\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"মেসেজগুলো\",\n      \"showMore\": \"আরো দেখুন\",\n      \"noMessages\": \"কোনো নতুন মেসেজ নেই\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"পরিকল্পিত রুমগুলো\",\n      \"noneFound\": \"পাওয়া যাইনি কোনো\",\n      \"allRooms\": \"সব পরিকল্পিত রুমগুলো\",\n      \"myRooms\": \"আমার পরিকল্পিত রুমগুলো\",\n      \"scheduleRoomHeader\": \"রুম পরিকল্পনা করুন\",\n      \"startRoom\": \"রুম শুরু করুন\",\n      \"modal\": {\n        \"needsFuture\": \"ভবিষ্যতের জন্য চাই\",\n        \"roomName\": \"রুমের নাম\",\n        \"minLength\": \"মিনিমাম ২ অক্ষরের\",\n        \"roomDescription\": \"রুমের বর্ণনা\"\n      },\n      \"tommorow\": \"আগামীকাল\",\n      \"today\": \"আজ\",\n      \"deleteModal\": {\n        \"areYouSure\": \"আপনি কি নিশ্চিত আপনি এই পরিকল্পিত রুমটি ডিলিট করতে চান?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"চ্যাট\",\n      \"emotesSoon\": \"[emotes শীঘ্রই আসছে]\",\n      \"bannedAlert\": \"আপনি চ্যাট থেকে ব্যান হয়েছেন\",\n      \"waitAlert\": \"একটু অপেক্ষা করুন আরেকটা মেসেজ করার আগে\",\n      \"search\": \"খুঁজুন\",\n      \"searchResults\": \"খুঁজে পাওয়া ফলাফল\",\n      \"recent\": \"সাম্প্রতিক\",\n      \"sendMessage\": \"মেসেজ পাঠান\",\n      \"whisper\": \"ফিসফিস\",\n      \"welcomeMessage\": \"চ্যাট এ স্বাগতম!\",\n      \"roomDescription\": \"রুমের বর্ণনা\",\n      \"disabled\": \"রুম চ্যাট অফ করা হয়েছে\",\n      \"messageDeletion\": {\n        \"message\": \"মেসেজ\",\n        \"retracted\": \"ডিলিট করেছেন\",\n        \"deleted\": \"ডিলিট করা হয়েছে\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"রকেটে জ্বালানি দেওয়া হচ্ছে\",\n      \"takingOff\": \"রকেট ছাড়ছে\",\n      \"inSpace\": \"মহাকাশে আছে\",\n      \"approachingMoon\": \"চাঁদের নিকটে\",\n      \"lunarDoge\": \"চন্দ্র Doge\",\n      \"approachingSun\": \"সূর্যের নিকটে\",\n      \"solarDoge\": \"সৌর Doge\",\n      \"approachingGalaxy\": \"গ্যালাক্সি এর নিকটে\",\n      \"galacticDoge\": \"গ্যালাকটিক Doge\",\n      \"spottedLife\": \"গ্রহে জীবনের সন্ধান পাওয়া গেছে\"\n    },\n    \"feed\": { \"yourFeed\": \"আপনার ফিড\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/bottom/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"💖✨✨🥺,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖,👉👈\",\n    \"loading\": \"💖✨✨🥺,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖💖👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈\",\n    \"noUsersFound\": \"💖✨✨🥺,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,,👉👈\",\n    \"ok\": \"💖💖✨,👉👈💖💖🥺,,👉👈\",\n    \"yes\": \"💖💖✨✨,👉👈💖💖,👉👈💖💖✨🥺👉👈\",\n    \"no\": \"💖💖✨👉👈💖💖✨,👉👈\",\n    \"cancel\": \"💖✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈💖💖🥺,,,👉👈\",\n    \"save\": \"💖✨✨✨,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈\",\n    \"edit\": \"💖✨🥺,,,,👉👈💖💖👉👈💖💖🥺👉👈💖💖✨🥺,👉👈\",\n    \"delete\": \"💖✨🥺,,,👉👈💖💖,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n    \"joinRoom\": \"💖✨✨,,,,👉👈💖💖✨,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n    \"copyLink\": \"💖✨🥺,,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖🥺,,👉👈\",\n    \"copied\": \"💖✨🥺,,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖🥺👉👈💖💖,👉👈💖💖👉👈✨✨✨,,,👉👈\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"💖✨✨✨👉👈💖💖🥺,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖✨👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈💖✨✨,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈💖✨✨,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈\",\n    \"mutedTitle\": \"💖✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨✨,,,,👉👈✨✨✨,,👉👈💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈💖✨✨,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"💖✨✨🥺,,,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖,,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨✨,👉👈\",\n    \"link_2\": \"💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖👉👈\",\n    \"link_3\": \"💖✨✨✨,,👉👈💖💖,👉👈💖💖✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖✨🥺,👉👈💖💖✨🥺,,👉👈💖💖,,,👉👈\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖,,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈✨✨✨✨🥺,👉👈\",\n      \"currentRoom\": \"💖✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈💖🥺,,,👉👈\",\n      \"startPrivateRoom\": \"💖✨✨✨,,,👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖🥺,,,,👉👈\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"💖✨✨👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈\",\n      \"followingHim\": \"💖✨✨👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"💖✨✨🥺,,,👉👈💖💖,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n      \"editRoom\": \"💖✨🥺,,,,👉👈💖💖👉👈💖💖🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n      \"refresh\": \"💖✨✨✨,,👉👈💖💖,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖,,,,👉👈\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖,👉👈💖🥺,,,,👉👈✨✨✨,,👉👈💖💖,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,👉👈\",\n      \"shareRoomLink\": \"💖✨✨✨,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖🥺,,👉👈\",\n      \"inviteFollowers\": \"💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,👉👈💖🥺,,,👉👈\",\n      \"whenFollowersOnline\": \"💖✨✨✨🥺,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,👉👈✨✨✨✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨,,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨✨🥺,👉👈\"\n    },\n    \"login\": {\n      \"headerText\": \"💖✨✨✨,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,👉👈💖💖✨,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖✨👉👈✨✨✨,,👉👈✨✨✨✨👉👈💖💖💖🥺,,,,👉👈💖💖💖,,,,👉👈💖💖✨✨🥺,,,👉👈\",\n      \"featureText_1\": \"💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖🥺,,👉👈✨✨✨,,👉👈💖✨✨✨,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n      \"featureText_2\": \"💖✨✨🥺,,,,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖🥺👉👈💖💖,,,👉👈💖💖✨👉👈✨✨✨✨🥺👉👈💖✨✨✨🥺👉👈💖💖✨,,👉👈💖💖✨🥺👉👈\",\n      \"featureText_3\": \"💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈✨✨✨✨🥺👉👈💖✨✨✨👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖✨🥺,,👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈\",\n      \"featureText_4\": \"💖✨✨🥺,,,,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈\",\n      \"featureText_5\": \"💖✨✨✨,,,,👉👈💖💖,👉👈💖💖✨✨👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨🥺,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈\",\n      \"featureText_6\": \"💖✨✨✨👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"loginGithub\": \"💖✨✨🥺,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖✨✨,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖✨✨,,👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈\",\n      \"loginTwitter\": \"💖✨✨🥺,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖✨✨✨,,,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n      \"createTestUser\": \"💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨,,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"💖✨✨🥺,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈\",\n      \"probablyLoading\": \"💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖💖👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈\",\n      \"voiceSettings\": \"💖✨✨✨🥺,👉👈💖💖✨,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈\",\n      \"overlaySettings\": \"💖✨✨🥺,,,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈\",\n      \"soundSettings\": \"💖✨✨✨,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈\",\n      \"deleteAccount\": \"💖✨🥺,,,👉👈💖💖,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,👉👈\",\n      \"couldNotFindUser\": \"💖✨✨✨,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨,,,,👉👈💖💖✨✨,👉👈✨✨✨✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"💖✨✨✨🥺,,👉👈💖💖,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖✨🥺👉👈✨✨✨,,,👉👈✨✨✨,,👉👈💖✨✨✨,,,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈✨✨✨✨🥺,👉👈\",\n      \"goHomeMessage\": \"💖✨✨🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨,,,,👉👈💖💖✨✨,👉👈✨✨✨✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈\",\n      \"goHomeLinkText\": \"💖💖,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\"\n    },\n    \"room\": {\n      \"speakers\": \"💖✨✨✨,,,👉👈💖💖✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n      \"requestingToSpeak\": \"💖✨✨✨,,👉👈💖💖,👉👈💖💖✨,,,👉👈💖💖✨🥺,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈\",\n      \"listeners\": \"💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n      \"allowAll\": \"💖✨🥺👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈\",\n      \"allowAllConfirm\": \"💖✨🥺👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖✨,,,👉👈✨✨✨,,👉👈💖✨✨✨,,,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈✨✨✨,,👉👈💖💖✨✨,,,👉👈💖💖✨✨,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖✨✨🥺👉👈💖💖✨✨🥺👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨,,,👉👈💖💖✨🥺,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈\"\n    },\n    \"searchUser\": {\n      \"search\": \"💖💖✨🥺👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈\"\n    },\n    \"soundEffectSettings\": {\n      \"header\": \"💖✨✨✨,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖👉👈💖💖✨🥺👉👈\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"💖✨🥺,,,,👉👈💖💖👉👈💖💖🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖,👉👈\",\n      \"followsYou\": \"💖✨✨👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈\",\n      \"followers\": \"💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n      \"following\": \"💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈\",\n      \"followHim\": \"💖✨✨👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈\",\n      \"unfollow\": \"💖✨✨✨🥺👉👈💖💖✨👉👈💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈\",\n      \"followingHim\": \"💖✨✨👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈\",\n      \"copyProfileUrl\": \"💖✨🥺,,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨🥺👉👈💖✨✨✨,,👉👈💖✨✨🥺,👉👈\",\n      \"urlCopied\": \"💖✨✨✨🥺👉👈💖✨✨✨,,👉👈💖✨✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖🥺👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖👉👈\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"💖✨✨✨🥺,👉👈💖💖✨,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈\",\n      \"mic\": \"💖✨✨🥺,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖🥺,,,👉👈\",\n      \"permissionError\": \"💖✨✨🥺,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨✨,,,,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖🥺,,,👉👈💖💖✨🥺,,👉👈💖💖,,,👉👈💖💖,,,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖,,,👉👈💖💖🥺👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,,👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈✨✨✨✨🥺,👉👈\",\n      \"refresh\": \"💖✨✨✨,,👉👈💖💖,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈✨✨✨,,👉👈💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈\",\n      \"volume\": \"💖✨✨✨🥺,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈💖🥺,,,👉👈\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"💖✨✨🥺,,,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈\",\n      \"input\": {\n        \"errorMsg\": \"💖✨✨✨👉👈💖💖🥺,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,👉👈💖💖✨,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖🥺,,,👉👈💖💖,👉👈\",\n        \"label\": \"💖✨🥺👉👈💖💖✨,,👉👈💖💖✨,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖🥺,,,👉👈💖💖,👉👈\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n      \"unban\": \"💖✨✨✨🥺👉👈💖💖✨👉👈💖✨✨✨✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈\",\n      \"noBans\": \"💖✨✨🥺,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖,👉👈💖💖✨🥺,👉👈\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"💖✨✨🥺,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n      \"confirmLeaveRoom\": \"💖✨🥺👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖✨,,,👉👈\",\n      \"leave\": \"💖✨✨🥺,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈\",\n      \"inviteUsersToRoomBtn\": \"💖✨✨,,,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n      \"invite\": \"💖✨✨,,,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n      \"toggleMuteMicBtn\": \"💖✨✨✨,,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,,,👉👈💖💖🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n      \"mute\": \"💖✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n      \"unmute\": \"💖✨✨✨🥺👉👈💖💖✨👉👈💖💖🥺,,,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n      \"makeRoomPublicBtn\": \"💖✨✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈✨✨✨,,,👉👈\",\n      \"settings\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈\",\n      \"speaker\": \"💖✨✨✨,,,👉👈💖💖✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n      \"listener\": \"💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n      \"chat\": \"💖✨🥺,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖👉👈💖💖,👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈✨✨✨✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈\",\n      \"linkText\": \"💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖✨✨,,👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈\",\n      \"addSupport\": \"💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖👉👈💖💖👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖👉👈💖💖,👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈✨✨✨✨🥺,👉👈\"\n    },\n    \"followingOnline\": {\n      \"people\": \"💖✨✨✨👉👈💖💖,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖🥺,,,👉👈💖💖,👉👈\",\n      \"online\": \"💖✨✨🥺,,,,👉👈💖✨✨🥺,,,👉👈💖✨✨🥺,👉👈💖✨✨,,,👉👈💖✨✨🥺,,,👉👈💖✨🥺,,,,👉👈\",\n      \"noOnline\": \"💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈✨✨✨✨🥺,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖,👉👈💖💖✨👉👈💖💖👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖,,,👉👈💖💖,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈\",\n      \"showMore\": \"💖✨✨✨,,,👉👈💖💖,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖,👉👈\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"💖✨✨,,,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,,👉👈\",\n      \"inviteToRoom\": \"💖✨✨,,,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"💖✨✨✨👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖👉👈💖💖,👉👈💖💖✨👉👈💖💖🥺👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,,,,👉👈💖💖✨✨,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈✨✨✨,,👉👈✨✨✨✨👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈✨✨✨✨,👉👈\",\n      \"dismiss\": \"💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈\",\n      \"tryAgain\": \"💖✨✨✨,,,,👉👈💖💖✨,,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨🥺👉👈💖💖,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺👉👈💖💖✨👉👈\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨🥺👉👈💖💖,👉👈💖💖✨✨,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖👉👈\",\n      \"listening\": \"💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈✨✨✨✨🥺,👉👈\",\n      \"toggleMuteKeybind\": \"💖✨✨✨,,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,,,👉👈💖💖🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨✨,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖👉👈\",\n      \"toggleOverlayKeybind\": \"💖✨✨✨,,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,,,👉👈💖💖🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨✨,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖👉👈\",\n      \"togglePushToTalkKeybind\": \"💖✨✨✨,,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,,,👉👈💖💖🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,,,,👉👈✨✨✨✨🥺👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨✨🥺👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺,,👉👈✨✨✨,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨✨,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖👉👈\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"💖✨✨🥺,,,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖👉👈💖💖🥺👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺👉👈💖💖✨,👉👈💖💖✨👉👈\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"💖✨✨✨🥺👉👈💖💖✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨🥺👉👈\",\n      \"exploreMoreRooms\": \"💖✨🥺,,,,👉👈💖💖✨✨👉👈💖💖✨,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨🥺👉👈\"\n    },\n    \"addToCalendar\": {\n      \"add\": \"💖✨🥺👉👈💖💖👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈\"\n    },\n    \"wsKilled\": {\n      \"description\": \"💖✨✨✨🥺,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,,👉👈💖✨✨✨,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖🥺,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨,,,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,,👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈✨✨✨✨🥺,👉👈\",\n      \"reconnect\": \"💖✨✨✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖✨👉👈💖💖,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈\"\n    },\n    \"search\": {\n      \"placeholder\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨🥺👉👈✨✨✨✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖,👉👈💖💖✨🥺👉👈\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"💖✨✨✨👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖,,👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖,👉👈\",\n      \"language\": \"💖✨✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"reportABug\": \"💖✨✨✨,,👉👈💖💖,👉👈💖💖✨,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨🥺👉👈✨✨✨,,👉👈💖✨🥺,👉👈💖💖✨🥺,,👉👈💖💖,,,👉👈\",\n      \"useOldVersion\": \"💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨🥺,,,,👉👈💖💖🥺,,,👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈\",\n      \"logOut\": {\n        \"button\": \"💖✨✨🥺,👉👈💖💖✨,👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈\",\n        \"modalSubtitle\": \"💖✨🥺👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖✨,,,👉👈\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"💖✨✨👉👈💖💖🥺👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖🥺👉👈💖💖,👉👈💖💖🥺,,,👉👈💖💖👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖💖✨👉👈💖💖,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n        \"public\": \"💖✨✨✨👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈\",\n        \"private\": \"💖✨✨✨👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n        \"roomName\": \"💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n        \"roomDescription\": \"💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖👉👈💖💖,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈\",\n        \"descriptionError\": \"💖✨✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖,,,👉👈✨✨✨✨🥺,,,👉👈✨✨✨✨🥺,,,👉👈\",\n        \"nameError\": \"💖✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖✨🥺,,,,👉👈💖💖,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖,,,,👉👈✨✨✨✨🥺,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖,,,👉👈\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"💖✨✨🥺,,,👉👈💖💖,👉👈💖💖✨🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈\",\n        \"roomInviteFrom\": \"💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨,,,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n        \"justStarted\": \"💖✨✨✨,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖🥺,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈\",\n        \"likeToJoin\": \"✨✨✨✨,,,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖🥺,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖🥺,👉👈💖💖✨,👉👈💖💖🥺👉👈💖💖✨👉👈💖✨,,,👉👈\",\n        \"inviteReceived\": \"💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨🥺,,,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨👉👈\",\n        \"avatarUrlError\": \"💖✨✨,,,👉👈💖💖✨👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈\",\n        \"avatarUrlLabel\": \"💖✨✨,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖✨✨,,👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈✨✨✨✨🥺,,👉👈💖✨✨✨,,,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨✨✨🥺👉👈💖✨✨✨,,👉👈💖✨✨🥺,👉👈\",\n        \"displayNameError\": \"💖✨✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖,,,👉👈✨✨✨✨🥺,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n        \"displayNameLabel\": \"💖✨🥺,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨,,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n        \"usernameError\": \"💖✨✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈✨✨✨✨🥺,,,,👉👈💖,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈✨✨✨✨🥺,,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖,👉👈\",\n        \"usernameLabel\": \"💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n        \"bioError\": \"💖✨✨🥺,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖,,👉👈✨✨✨,,👉👈✨✨✨✨🥺,,,,👉👈💖,,,,👉👈✨✨✨✨🥺,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖✨🥺👉👈\",\n        \"bioLabel\": \"💖✨🥺,👉👈💖💖🥺👉👈💖💖✨,👉👈\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"💖✨🥺👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖🥺,👉👈💖💖✨,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨✨,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖✨,,,👉👈\",\n        \"blockUser\": \"💖✨🥺,👉👈💖💖🥺,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n        \"makeMod\": \"💖✨✨✨👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖✨,👉👈💖💖👉👈\",\n        \"unmod\": \"💖✨🥺,,,👉👈💖💖,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖✨,👉👈💖💖👉👈\",\n        \"addAsSpeaker\": \"💖✨🥺👉👈💖💖👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨✨,,,👉👈💖💖✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n        \"moveToListener\": \"💖✨✨🥺,,👉👈💖💖✨,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n        \"banFromChat\": \"💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨🥺,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈\",\n        \"banFromRoom\": \"💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n        \"goBackToListener\": \"💖✨✨,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨🥺,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n        \"deleteMessage\": \"💖✨🥺,,,👉👈💖💖,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈\",\n        \"makeRoomCreator\": \"💖✨✨✨👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨🥺👉👈💖💖👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"💖💖✨,,,,👉👈💖💖,👉👈💖💖✨,,,👉👈💖💖✨🥺,,👉👈💖💖🥺👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨,,,,👉👈💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈\",\n        \"makePublic\": \"💖💖🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈\",\n        \"makePrivate\": \"💖💖🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈\",\n        \"renamePublic\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨🥺,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n        \"renamePrivate\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": {\n      \"yourFeed\": \"💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖,👉👈💖💖,👉👈💖💖👉👈\"\n    },\n    \"scheduledRooms\": {\n      \"title\": \"💖✨✨✨,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨🥺👉👈\",\n      \"noneFound\": \"💖💖✨👉👈💖💖✨,👉👈💖💖✨👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖💖👉👈\",\n      \"allRooms\": \"💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖💖🥺,,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨🥺👉👈\",\n      \"myRooms\": \"💖💖🥺,,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖✨🥺👉👈\",\n      \"scheduleRoomHeader\": \"💖✨✨✨,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n      \"startRoom\": \"💖💖✨🥺👉👈💖💖✨🥺,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈\",\n      \"modal\": {\n        \"needsFuture\": \"💖💖✨👉👈💖💖,👉👈💖💖,👉👈💖💖👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨🥺,,👉👈💖💖✨🥺,👉👈💖💖✨🥺,,👉👈💖💖✨,,,,👉👈💖💖,👉👈\",\n        \"roomName\": \"💖💖✨,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈\",\n        \"roomDescription\": \"💖✨🥺,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈\",\n        \"minLength\": \"💖💖🥺,,,,👉👈💖💖🥺👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖,,,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖👉👈\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"💖✨🥺,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈\",\n      \"emotesSoon\": \"💖✨✨✨✨,👉👈💖💖,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖✨🥺👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖✨👉👈💖✨✨✨✨,,,👉👈\",\n      \"bannedAlert\": \"💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈💖💖,👉👈💖💖✨👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨👉👈💖💖,👉👈💖💖👉👈✨✨✨,,👉👈💖💖,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈\",\n      \"waitAlert\": \"💖✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨🥺,,👉👈✨✨✨,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,👉👈💖💖,👉👈💖💖,,👉👈💖💖✨,👉👈💖💖✨,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖✨👉👈💖💖👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈💖💖,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"search\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈\",\n      \"searchResults\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖✨✨✨,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖🥺,,,👉👈💖💖✨🥺,👉👈💖💖✨🥺👉👈\",\n      \"recent\": \"💖✨✨👉👈💖💖✨,,,,👉👈💖💖,👉👈💖💖✨,,,👉👈💖💖✨🥺,,👉👈💖💖,👉👈💖💖✨👉👈💖💖✨🥺,👉👈💖💖🥺,,,👉👈💖💖✨✨,👉👈✨✨✨,,👉👈💖✨✨✨🥺👉👈💖💖✨🥺👉👈💖💖,👉👈💖💖👉👈\",\n      \"sendMessage\": \"💖✨✨✨,,,👉👈💖💖,👉👈💖💖✨👉👈💖💖👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,👉👈✨✨✨,,👉👈💖✨✨🥺,,👉👈💖💖,👉👈💖💖✨🥺👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"whisper\": \"💖✨✨✨🥺,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨🥺👉👈💖💖✨,,👉👈💖💖,👉👈💖💖✨,,,,👉👈\",\n      \"welcomeMessage\": \"💖✨✨✨🥺,,👉👈💖💖,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺,👉👈💖💖✨,👉👈✨✨✨,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨🥺,👉👈✨✨✨,,,👉👈\",\n      \"roomDescription\": \"💖✨✨✨,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖🥺,,,,👉👈✨✨✨,,👉👈💖💖👉👈💖💖,👉👈💖💖✨🥺👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨,,,,👉👈💖💖🥺👉👈💖💖✨,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖💖✨,👉👈💖💖✨👉👈\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"💖✨✨👉👈💖💖✨🥺,,👉👈💖💖,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖🥺,,👉👈💖💖,👉👈💖💖✨🥺,👉👈\",\n      \"takingOff\": \"💖✨✨✨,,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨,👉👈💖💖,,👉👈💖💖,,👉👈\",\n      \"inSpace\": \"💖✨✨,,,👉👈💖💖✨👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,👉👈\",\n      \"approachingMoon\": \"💖✨🥺👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖🥺,,,,👉👈💖💖✨,👉👈💖💖✨,👉👈💖💖✨👉👈\",\n      \"lunarDoge\": \"💖✨✨🥺,👉👈💖💖✨🥺,,👉👈💖💖✨👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"approachingSun\": \"💖✨🥺👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨🥺,,👉👈💖💖✨👉👈\",\n      \"solarDoge\": \"💖✨✨✨,,,👉👈💖💖✨,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨,,,,👉👈✨✨✨,,👉👈💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"approachingGalaxy\": \"💖✨🥺👉👈💖💖✨,,👉👈💖💖✨,,👉👈💖💖✨,,,,👉👈💖💖✨,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖,,,,👉👈💖💖🥺👉👈💖💖✨👉👈💖💖,,,👉👈✨✨✨,,👉👈💖💖,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨✨👉👈💖💖✨✨,👉👈\",\n      \"galacticDoge\": \"💖✨✨,👉👈💖✨✨✨✨🥺,,👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖✨✨✨✨🥺,,,,👉👈💖💖✨🥺,👉👈💖💖🥺👉👈💖✨✨✨✨🥺,,,,👉👈✨✨✨,,👉👈💖✨🥺,,,👉👈💖💖✨,👉👈💖💖,,,👉👈💖💖,👉👈\",\n      \"spottedLife\": \"💖✨✨✨👉👈💖💖🥺,,,👉👈💖✨✨✨✨🥺,,👉👈💖💖✨👉👈💖💖,👉👈💖💖✨🥺,👉👈✨✨✨,,👉👈💖💖✨🥺,,,,👉👈💖💖🥺👉👈💖💖✨🥺,👉👈💖💖,,,,👉👈✨✨✨,,👉👈💖💖🥺,,,👉👈💖💖🥺👉👈💖💖,,👉👈💖💖,👉👈✨✨✨,,👉👈💖💖✨🥺👉👈💖💖✨,,👉👈💖💖✨,👉👈💖💖✨🥺,👉👈💖💖✨🥺,👉👈💖💖,👉👈💖💖👉👈\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/cs/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Načíst více\",\n    \"loading\": \"Načítám...\",\n    \"noUsersFound\": \"Nikdo tu není\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ano\",\n    \"no\": \"Ne\",\n    \"cancel\": \"Zrušit\",\n    \"save\": \"Uložit\",\n    \"edit\": \"Pozměnit\",\n    \"delete\": \"Smazat\",\n    \"joinRoom\": \"Připojit se do místnosti\",\n    \"copyLink\": \"Kopírovat odkaz\",\n    \"copied\": \"Zkopírováno!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Prosím, dej DogeHousu práva na \\\"Dostupnost\\\".\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Ztlumen | DogeHouse\",\n    \"deafenedTitle\": \"Vypnutý zvuk | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Původ\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Nahlásit chybu\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Zabanovat\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"důvod\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Seznam uživatelů které sleduješ a nejsou v soukromé místnosti\",\n      \"currentRoom\": \"Právě v:\",\n      \"startPrivateRoom\": \"Otevřít soukromou místnost\",\n      \"title\": \"Lidé\"\n    },\n    \"followList\": {\n      \"followHim\": \"sledovat\",\n      \"followingHim\": \"sleduješ\",\n      \"title\": \"Lidé\",\n      \"followingNone\": \"Nesleduješ nikoho\",\n      \"noFollowers\": \"Nemáš žádné sledující\"\n    },\n    \"home\": {\n      \"createRoom\": \"Vytvořit místnost\",\n      \"refresh\": \"Obnovit\",\n      \"editRoom\": \"Upravit místnost\",\n      \"desktopAlert\": \"Stáhni si desktopovou aplikaci DogeHouse již dnes!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Místnost není dostupná, jít zpět\",\n      \"shareRoomLink\": \"Sdílet odkaz na místnost\",\n      \"inviteFollowers\": \"Můžeš pozvat své následovníky, kteří jsou online:\",\n      \"whenFollowersOnline\": \"Až budou tví následovníci online, objeví se zde.\"\n    },\n    \"login\": {\n      \"headerText\": \"Voicechat, který stoupá ke hvězdám 🚀\",\n      \"featureText_1\": \"Tmavý motiv\",\n      \"featureText_2\": \"Otevřené registrace\",\n      \"featureText_3\": \"Funguje na všech platformách\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Textový Chat\",\n      \"featureText_6\": \"Doporučuje 10/10 hafanů\",\n      \"loginGithub\": \"Přihlásit se přes GitHub\",\n      \"loginTwitter\": \"Přihlásit se přes Twitter\",\n      \"createTestUser\": \"Vytvořit testovacího uživatele\",\n      \"loginDiscord\": \"Přihlásit se přes Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Odhlásit se\",\n      \"probablyLoading\": \"Nejspíš se to načítá...\",\n      \"voiceSettings\": \"Nastavení hlasu\",\n      \"soundSettings\": \"Nastavení zvuku\",\n      \"deleteAccount\": \"Smazat účet\",\n      \"overlaySettings\": \"Nastavení overlaye\",\n      \"couldNotFindUser\": \"Uživatel bohužel nenalezen...\",\n      \"privacySettings\": \"Nastavení soukromí\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Upst! Tahle místnost se ztratila v konverzaci.\",\n      \"goHomeMessage\": \"Netřeba se strachovat. Můžeš\",\n      \"goHomeLinkText\": \"přejít na hlavní stránku\"\n    },\n    \"room\": {\n      \"speakers\": \"Mluvící\",\n      \"requestingToSpeak\": \"Požaduje oprávnění mluvit\",\n      \"listeners\": \"Posluchači\",\n      \"allowAll\": \"Povolit všem\",\n      \"allowAllConfirm\": \"Jsi si jistý? Tohle povolí všem {{count}} žádajícím uživatelům mluvit\"\n    },\n    \"searchUser\": { \"search\": \"Hledat...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Zvuky\",\n      \"title\": \"Nastavení zvuku\",\n      \"playSound\": \"Přehrát zvuk\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Upravit profil\",\n      \"followsYou\": \"Sleduje tě.\",\n      \"followers\": \"Sledující\",\n      \"following\": \"Sleduje\",\n      \"followHim\": \"Sledovat\",\n      \"followingHim\": \"Sleduješ\",\n      \"copyProfileUrl\": \"Zkopírovat URL profilu\",\n      \"urlCopied\": \"URL zkopírováno do schránky\",\n      \"unfollow\": \"Přestat sledovat\",\n      \"about\": \"O uživateli\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"O uživateli\",\n        \"rooms\": \"Místnosti\",\n        \"scheduled\": \"Naplánováno\",\n        \"recorded\": \"Nahráno\",\n        \"clips\": \"Klipy\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Blokovat\",\n      \"unblock\": \"Odblokovat\",\n      \"sendDM\": \"Poslat zprávu\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Nastavení hlasu\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Buď nemáš mikrofon, nebo jsi jej nepovolil/a.\",\n      \"refresh\": \"Znovu načíst mikrofon\",\n      \"volume\": \"Hlasitost:\",\n      \"title\": \"Nastavení hlasu\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Neplatný název aplikace\",\n        \"label\": \"Zadej název aplikace\"\n      },\n      \"header\": \"Nastavení overlaye\",\n      \"errorMsg\": \"Zadej platný název aplikace\",\n      \"label\": \"Zadej název aplikace\"\n    },\n    \"download\": {\n      \"starting\": \"Zahájení stahování...\",\n      \"failed\": \"Stahování selhalo, prosím zkus to později\",\n      \"visit_gh\": \"Navštívit Github Releases\",\n      \"prompt\": \"Kliknutím na tlačítko níže zahájíš stahování\",\n      \"download_now\": \"Stáhnout nyní\",\n      \"download_for\": \"Stáhnout pro %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Nastavení soukromí\",\n      \"header\": \"Nastavení soukromí\",\n      \"whispers\": {\n        \"label\": \"Zprávy šeptem\",\n        \"on\": \"Zapnuté\",\n        \"off\": \"Vypnuté\"\n      }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Zabanovaní uživatelé\",\n      \"unban\": \"Odbanovat\",\n      \"noBans\": \"Nikdo není zabanovaný.\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Opustit místnost\",\n      \"confirmLeaveRoom\": \"Určitě opustit místnost?\",\n      \"leave\": \"Opustit\",\n      \"inviteUsersToRoomBtn\": \"Pozvat někoho\",\n      \"invite\": \"Pozvat\",\n      \"toggleMuteMicBtn\": \"Zapnout/vypnout mikrofon\",\n      \"mute\": \"Ztlumit mikrofon\",\n      \"unmute\": \"Zrušit ztlumení mikrofonu\",\n      \"makeRoomPublicBtn\": \"Udělat místnost veřejnou\",\n      \"settings\": \"Nastavení\",\n      \"speaker\": \"Mluvící\",\n      \"listener\": \"Posluchač\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Zapnout/vypnout zvuk\",\n      \"deafen\": \"Ztlumit zvuk\",\n      \"undeafen\": \"Zrušit ztlumení zvuku\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Tvé zařízení není momentálně podporováno. Můžeš o podporu\",\n      \"linkText\": \"požádat na GitHubu,\",\n      \"addSupport\": \"a pokusíme se podporu přidat.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Pozván\",\n      \"inviteToRoom\": \"Pozvat do místnosti\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Nemáš povolený mikrofon! Zkus znovu načíst stránku.\",\n      \"dismiss\": \"Zavřít\",\n      \"tryAgain\": \"Zkusit znovu\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Nastavit klávesovou zkratku\",\n      \"listening\": \"Poslouchám...\",\n      \"toggleMuteKeybind\": \"Zapnout/vypnout mikrofon\",\n      \"togglePushToTalkKeybind\": \"Push-To-Talk (mluvit jen po zmáčknutí tlačítka)\",\n      \"toggleOverlayKeybind\": \"Zapnout/vypnout overlay\",\n      \"toggleDeafKeybind\": \"Zapnout/vypnout zvuk\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Z nějakého důvodu není vysílán zvuk\"\n    },\n    \"addToCalendar\": { \"add\": \"Přidat do kalendáře\" },\n    \"wsKilled\": {\n      \"description\": \"Server tě odpojil. Neotevřel jsi další okno?\",\n      \"reconnect\": \"Připojit se znovu\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Veřejná\",\n        \"private\": \"Soukromá\",\n        \"roomName\": \"Název místnosti\",\n        \"roomDescription\": \"Popis místnosti\",\n        \"descriptionError\": \"Maximální délka: 500\",\n        \"nameError\": \"Musí být 2 až 60 znaků\",\n        \"subtitle\": \"Prosím, vyplň následující pole:\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nová místnost vytvořena.\",\n        \"roomInviteFrom\": \"Pozvánka od\",\n        \"justStarted\": \"Právě začali\",\n        \"likeToJoin\": \", chceš se přidat?\",\n        \"inviteReceived\": \"Byl jsi pozván do\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Jméno zabráno.\",\n        \"avatarUrlError\": \"Neplatný obrázek\",\n        \"avatarUrlLabel\": \"Odkaz na avatar z Githubu či Twitteru\",\n        \"displayNameError\": \"2-50 znaků!\",\n        \"displayNameLabel\": \"Zobrazované jméno\",\n        \"usernameError\": \"4-15 znaků bez diakritiky\",\n        \"usernameLabel\": \"Uživatelské jméno\",\n        \"bioError\": \"Maximálně 160 znaků\",\n        \"bioLabel\": \"O mně\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Chceš zablokovat uživatele ze všech místností, co kdy vytvoříš?\",\n        \"blockUser\": \"Zablokovat\",\n        \"makeMod\": \"Přidat práva moderátora\",\n        \"unmod\": \"Odstranit práva moderátora\",\n        \"addAsSpeaker\": \"Přidat jako mluvícího\",\n        \"moveToListener\": \"Umlčet\",\n        \"banFromChat\": \"Zabanovat z chatu\",\n        \"banFromRoom\": \"Zabanovat z místnosti\",\n        \"goBackToListener\": \"Přesunout se do posluchačů\",\n        \"deleteMessage\": \"Smazat tuto zprávu\",\n        \"makeRoomCreator\": \"Předat místnost\",\n        \"unBanFromChat\": \"Odbanovat z chatu\",\n        \"banIPFromRoom\": \"Zabanovat IP z místnosti\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Požádat o přesunutí do mluvících\",\n        \"makePublic\": \"Udělat místnost veřejnou\",\n        \"makePrivate\": \"Udělat místnost soukromou\",\n        \"renamePublic\": \"Nastavit jméno veřejné místnosti\",\n        \"renamePrivate\": \"Nastavit jméno soukromé místnosti\",\n        \"chatDisabled\": \"Vypnout chat\",\n        \"chatCooldown\": \"Chat Cooldown (v milisekundách)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Zapnut\",\n          \"disabled\": \"Vypnut\",\n          \"followerOnly\": \"Pouze pro sledující\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Lidé\",\n      \"online\": \"PRÁVĚ AKTIVNÍ\",\n      \"noOnline\": \"Nikdo z tvých přátel není právě aktivní.\",\n      \"showMore\": \"Načíst více\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Očekávané místnosti\",\n      \"exploreMoreRooms\": \"Hledat nové místnosti\"\n    },\n    \"search\": {\n      \"placeholder\": \"Hledat místnosti, uživatele, nebo kategorie\",\n      \"placeholderShort\": \"Hledat\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Jazyk (Language)\",\n      \"reportABug\": \"Nahlásit chybu\",\n      \"useOldVersion\": \"Přejít na starou verzi stránky\",\n      \"logOut\": {\n        \"button\": \"Odhlásit se\",\n        \"modalSubtitle\": \"Určitě se chceš odhlásit?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Ladit zvuk\",\n        \"stopDebugger\": \"Zastavit ladění\"\n      },\n      \"downloadApp\": \"Stáhnout aplikaci\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Zprávy\",\n      \"showMore\": \"Ukázat více\",\n      \"noMessages\": \"Žádné nové zprávy\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Naplánované místnosti\",\n      \"noneFound\": \"Žádné nenalezeny\",\n      \"allRooms\": \"Všechny naplánované místnosti\",\n      \"myRooms\": \"Tvé naplánované místnosti\",\n      \"scheduleRoomHeader\": \"Naplánovat místnost\",\n      \"startRoom\": \"Otevřít místnost\",\n      \"modal\": {\n        \"needsFuture\": \"Musí být v budoucnosti\",\n        \"roomName\": \"Jméno místnosti\",\n        \"minLength\": \"Minimálně dva znaky\",\n        \"roomDescription\": \"Popis místnosti\"\n      },\n      \"tommorow\": \"ZÍTRA\",\n      \"today\": \"DNES\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Opravdu chceš smazat tuto naplánovanou místnost?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"Emoty \\\"již brzy\\\"\",\n      \"bannedAlert\": \"Jsi zabanovaný z chatu.\",\n      \"waitAlert\": \"Prosím, vyčkej pár sekund, než začneš zase psát!\",\n      \"search\": \"Hledat\",\n      \"searchResults\": \"Výsledky hledání\",\n      \"recent\": \"Často využívané\",\n      \"sendMessage\": \"Odeslat zprávu\",\n      \"whisper\": \"Šeptem\",\n      \"welcomeMessage\": \"Vítej v chatu!\",\n      \"roomDescription\": \"Popis místnosti\",\n      \"disabled\": \"Chat této místnosti byl vypnut\",\n      \"messageDeletion\": {\n        \"message\": \"zpráva\",\n        \"retracted\": \"odvolána\",\n        \"deleted\": \"smazána\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Plníme raketu\",\n      \"takingOff\": \"Vzlétáme\",\n      \"inSpace\": \"Ve vesmíru\",\n      \"approachingMoon\": \"Přibližujeme se k měsíci\",\n      \"lunarDoge\": \"Měsíční Doge\",\n      \"approachingSun\": \"Blížíme se ke Slunci\",\n      \"solarDoge\": \"Sluneční Doge\",\n      \"approachingGalaxy\": \"Vylétáme z Galaxie\",\n      \"galacticDoge\": \"Galaktický Doge\",\n      \"spottedLife\": \"Planeta se životem objevena\"\n    },\n    \"feed\": { \"yourFeed\": \"Tvůj Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/da/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Indlæs mere\",\n    \"loading\": \"Indlæser...\",\n    \"noUsersFound\": \"Ingen brugere fundet\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ja\",\n    \"no\": \"Nej\",\n    \"cancel\": \"Afbryd\",\n    \"save\": \"Gem\",\n    \"edit\": \"Rediger\",\n    \"delete\": \"Slet\",\n    \"joinRoom\": \"Tilslut rum\",\n    \"copyLink\": \"Kopier link\",\n    \"copied\": \"Kopieret\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please give DogeHouse Accessibility permessions\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Lydløs | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Forbindelse optaget\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Historie\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Rapporter en fejl\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Bandlys\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Liste af brugere du følger, som ikke er i et privat rum.\",\n      \"currentRoom\": \"tilsluttet til:\",\n      \"startPrivateRoom\": \"Start et privat rum med dem\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Start Rum\",\n      \"refresh\": \"Opdater\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"rummet er væk, gå tilbage\",\n      \"shareRoomLink\": \"Del et link til rummet\",\n      \"inviteFollowers\": \"Du kan invitere dine følgere, der er online:\",\n      \"whenFollowersOnline\": \"Når dine følgere er online, vil de være her.\"\n    },\n    \"login\": {\n      \"headerText\": \"Tager taleopkald til månen 🚀\",\n      \"featureText_1\": \"Mørk Tema\",\n      \"featureText_2\": \"Åben Tilmelding\",\n      \"featureText_3\": \"Tvær-platform understøttelse\",\n      \"featureText_4\": \"Åben Kildekode\",\n      \"featureText_5\": \"Tekst Chat\",\n      \"featureText_6\": \"Drevet af Doge\",\n      \"loginGithub\": \"Log ind med GitHub\",\n      \"loginTwitter\": \"Log ind med Twitter\",\n      \"createTestUser\": \"Lav en test bruger\",\n      \"loginDiscord\": \"Log ind med Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Log ud\",\n      \"probablyLoading\": \"Indlæser sandsynligvis...\",\n      \"voiceSettings\": \"Gå til stemmeindstillinger\",\n      \"soundSettings\": \"Gå til lydindstillinger\",\n      \"deleteAccount\": \"Slet konto\",\n      \"overlaySettings\": \"Gå til overlay indstillinger\",\n      \"couldNotFindUser\": \"Vi kunne desværre ikke finde den bruger\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Denne side gik tabt i samtalen.\",\n      \"goHomeMessage\": \"Bare rolig. Du kan\",\n      \"goHomeLinkText\": \"Gå hjem\"\n    },\n    \"room\": {\n      \"speakers\": \"Højttalere\",\n      \"requestingToSpeak\": \"Anmoder om at tale\",\n      \"listeners\": \"Lyttere\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Er du sikker? Dette giver tilladelse til alle {{count}} brugere der anmoder om tilladelse til at tale\"\n    },\n    \"searchUser\": { \"search\": \"Søger...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Lyde\",\n      \"title\": \"Lydstillinger\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Rediger profil\",\n      \"followsYou\": \"Følger dig\",\n      \"followers\": \"Følgere\",\n      \"following\": \"Følger\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"Følger\",\n      \"copyProfileUrl\": \"Kopier profil url\",\n      \"urlCopied\": \"URL kopieret til udklipsholder\",\n      \"unfollow\": \"Følg ikke længere\",\n      \"about\": \"Om\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Lydindstillinger\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Ingen mikrofoner fundet, du har enten ikke koblet nogen til eller ikke givet denne hjemmeside tilladelse.\",\n      \"refresh\": \"Opdater mikrofonliste\",\n      \"volume\": \"Lydstyrke:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\",\n      \"errorMsg\": \"Please enter valid app title\",\n      \"label\": \"Enter app title\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download nu\",\n      \"download_for\": \"Download til %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bandlyste Brugere\",\n      \"unban\": \"Ophæv bandlysning\",\n      \"noBans\": \"Ingen er bandlyst endnu\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Forlad nuværende rum\",\n      \"confirmLeaveRoom\": \"Er du sikker på at du vil forlade rummet?\",\n      \"leave\": \"Forlad rummet\",\n      \"inviteUsersToRoomBtn\": \"Inviter brugere til rummet\",\n      \"invite\": \"Inviter\",\n      \"toggleMuteMicBtn\": \"Slå mikrofon til/fra\",\n      \"mute\": \"Slå mikrofon fra\",\n      \"unmute\": \"Slå mikrofon til\",\n      \"makeRoomPublicBtn\": \"Gør rummet offentligt!\",\n      \"settings\": \"Indstillinger\",\n      \"speaker\": \"Taler\",\n      \"listener\": \"Lytter\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Skift døvhed\",\n      \"deafen\": \"Gør døv\",\n      \"undeafen\": \"Stop døvhed\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Din enhed understøttes ikke lige nu. Du kan oprette et\",\n      \"linkText\": \"issue på GitHub\",\n      \"addSupport\": \"og så vil jeg prøve at tilføje understøttelse til din enhed.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"inviteret\",\n      \"inviteToRoom\": \"Inviter til rum\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Adgang nægtet ved tilslutning af din mikrofon (du bliver måske nød til at gå ind i dine browserindstillinger og genindlæse siden)\",\n      \"dismiss\": \"Ignorer\",\n      \"tryAgain\": \"Prøv igen\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Indstil genvej\",\n      \"listening\": \"Lytter\",\n      \"toggleMuteKeybind\": \"Slå mikrofon til/fra genvej\",\n      \"togglePushToTalkKeybind\": \"Slå push-to-talk til/fra genvej\",\n      \"toggleOverlayKeybind\": \"Skift overlay genvej\",\n      \"toggleDeafKeybind\": \"Skift døvhed genvej\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Ingen audio consumer af en eller anden grund\"\n    },\n    \"addToCalendar\": { \"add\": \"Tilføj til kalender\" },\n    \"wsKilled\": {\n      \"description\": \"Websocketen blev dræbt af serveren. Dette sker typisk når du har hjemmesiden åben i en anden fane.\",\n      \"reconnect\": \"Tilslut igen\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Offentlig\",\n        \"private\": \"Privat\",\n        \"roomName\": \"Rum navn\",\n        \"roomDescription\": \"Rum beskrivelse\",\n        \"descriptionError\": \"Max længde 500\",\n        \"nameError\": \"Skal være mellem 2 og 60 tegn langt\",\n        \"subtitle\": \"Udfyld følgende felter for at starte et nyt rum\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nyt rum oprettet\",\n        \"roomInviteFrom\": \"Rum Invitation fra\",\n        \"justStarted\": \"De er lige begyndt\",\n        \"likeToJoin\": \", Har du lyst til at deltage?\",\n        \"inviteReceived\": \"Du er blevet inviteret til\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Brugernavn optaget\",\n        \"avatarUrlError\": \"Ugyldigt billede\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"Længde 2 til 50 tegn\",\n        \"displayNameLabel\": \"Offentligt Navn\",\n        \"usernameError\": \"Længde 4 til 15 tegn og kun alphanumerisk/underscore\",\n        \"usernameLabel\": \"Brugernavn\",\n        \"bioError\": \"Max længde på 160 tegn\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Er du sikker på at du vil blokere denne bruger fra at tilslutte sig til alle de rum du nogensinde kommer til at lave?\",\n        \"blockUser\": \"Bloker bruger\",\n        \"makeMod\": \"Gør til moderator\",\n        \"unmod\": \"Fjern moderator\",\n        \"addAsSpeaker\": \"Tilføj som taler\",\n        \"moveToListener\": \"Flyt til lyttere\",\n        \"banFromChat\": \"Bandlys fra chatten\",\n        \"banFromRoom\": \"Bandlys fra rummet\",\n        \"goBackToListener\": \"Gå tilbage til lytter\",\n        \"deleteMessage\": \"Slet denne besked\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Fjern bandlysning fra chatten\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"kræv tilladelse for at tale\",\n        \"makePublic\": \"Gør rum offentligt\",\n        \"makePrivate\": \"Gør rum privat\",\n        \"renamePublic\": \"Skift navnet på offentligt rum\",\n        \"renamePrivate\": \"Skift navnet på privat rum\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Personer\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Du har 0 venner online lige nu\",\n      \"showMore\": \"Vis mere\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Kommende rum\",\n      \"exploreMoreRooms\": \"Udforsk flere rum\"\n    },\n    \"search\": {\n      \"placeholder\": \"Søg efter rum, brugere eller kategorier\",\n      \"placeholderShort\": \"Søg\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Sprog\",\n      \"reportABug\": \"Anmeld en fejl\",\n      \"useOldVersion\": \"Brug gammel version\",\n      \"logOut\": {\n        \"button\": \"Log ud\",\n        \"modalSubtitle\": \"Er du sikker på at du vil logge ud?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Planlagte Rum\",\n      \"noneFound\": \"Ingen fundet\",\n      \"allRooms\": \"Alle planlagte rum\",\n      \"myRooms\": \"Mine planlagte rum\",\n      \"scheduleRoomHeader\": \"Planlæg Rum\",\n      \"startRoom\": \"Start rum\",\n      \"modal\": {\n        \"needsFuture\": \"skal være i fremtiden\",\n        \"roomName\": \"Rum navn\",\n        \"minLength\": \"Minimum længde på 2 tegn\",\n        \"roomDescription\": \"Beskrivelse\"\n      },\n      \"tommorow\": \"I MORGEN\",\n      \"today\": \"I DAG\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Er du sikker på at du vil slette dette planlagte rum?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes kommer snart]\",\n      \"bannedAlert\": \"Du blev bandlyst fra chatten\",\n      \"waitAlert\": \"Du skal vente et sekund før du sender endnu en besked\",\n      \"search\": \"Søg\",\n      \"searchResults\": \"Søgeresultater\",\n      \"recent\": \"Ofte Anvendt\",\n      \"sendMessage\": \"Send en Besked\",\n      \"whisper\": \"Hvisk\",\n      \"welcomeMessage\": \"Velkommen til chatten!\",\n      \"roomDescription\": \"Rum beskrivelse\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Tanker raket\",\n      \"takingOff\": \"Tager afsted\",\n      \"inSpace\": \"I rummet\",\n      \"approachingMoon\": \"Nærmes månen\",\n      \"lunarDoge\": \"Måne doge\",\n      \"approachingSun\": \"Nærmes solen\",\n      \"solarDoge\": \"Sol doge\",\n      \"approachingGalaxy\": \"Nærmer galakse\",\n      \"galacticDoge\": \"Galaktisk Doge\",\n      \"spottedLife\": \"Fået øje på planet med liv\"\n    },\n    \"feed\": { \"yourFeed\": \"Din Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/de/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Mehr laden\",\n    \"loading\": \"Lädt...\",\n    \"noUsersFound\": \"Keine Benutzer gefunden\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ja\",\n    \"no\": \"Nein\",\n    \"cancel\": \"Abbrechen\",\n    \"save\": \"Speichern\",\n    \"edit\": \"Bearbeiten\",\n    \"delete\": \"Löschen\",\n    \"joinRoom\": \"Raum beitreten\",\n    \"copyLink\": \"Link kopieren\",\n    \"copied\": \"kopiert\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Bitte erteile DogeHouse Browserberechtigungen.\",\n    \"copy\": \"Kopieren\",\n    \"error\": \"Fehler\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Stumm | DogeHouse\",\n    \"deafenedTitle\": \"Lautsprecher stumm | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Verbindung getrennt\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ursprungsgeschichte\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Problem melden\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"blockieren\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Nutzername\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"Grund\",\n      \"usernamePlaceholder\": \"Benutzername, für den Aktionen ausgeführt werden sollen\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Benutzer, denen du folgst, die nicht in einem privaten Raum sind.\",\n      \"currentRoom\": \"Zurzeit in:\",\n      \"startPrivateRoom\": \"Einen privaten Raum mit diesem Benutzer eröffnen\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Folgen\",\n      \"followingHim\": \"Entfolgen\",\n      \"title\": \"People\",\n      \"followingNone\": \"Folgt keinem\",\n      \"noFollowers\": \"Keine Follower\"\n    },\n    \"home\": {\n      \"createRoom\": \"Raum erstellen\",\n      \"refresh\": \"Aktualisieren\",\n      \"editRoom\": \"Bearbeite Raum\",\n      \"desktopAlert\": \"Lade die DogeHouse Desktop-App noch heute herunter.\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Raum konnte nicht gefunden werden! Zurück\",\n      \"shareRoomLink\": \"Link zum Raum teilen\",\n      \"inviteFollowers\": \"Du kannst deine Abonnenten, die online sind, einladen:\",\n      \"whenFollowersOnline\": \"Wenn deine Abonnenten online sind, werden sie hier angezeigt.\"\n    },\n    \"login\": {\n      \"headerText\": \"Wir bringen den Sprachchat zum Mond\",\n      \"featureText_1\": \"Dark-Mode\",\n      \"featureText_2\": \"Unbeschränkte Registrierung\",\n      \"featureText_3\": \"Plattformunabhängig\",\n      \"featureText_4\": \"Open-Source\",\n      \"featureText_5\": \"Text-Chat\",\n      \"featureText_6\": \"Angetrieben durch Doge\",\n      \"loginGithub\": \"mit GitHub anmelden\",\n      \"loginTwitter\": \"mit Twitter anmelden\",\n      \"createTestUser\": \"Testnutzer erstellen\",\n      \"loginDiscord\": \"mit Discord anmelden\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Abmelden\",\n      \"probablyLoading\": \"Lädt vermutlich...\",\n      \"voiceSettings\": \"Spracheinstellungen\",\n      \"soundSettings\": \"Soundeinstellungen\",\n      \"deleteAccount\": \"Konto löschen\",\n      \"overlaySettings\": \"Overlay-Einstellungen löschen\",\n      \"couldNotFindUser\": \"Wir konnten diesen Benutzer leider nicht finden. \",\n      \"privacySettings\": \"Privacy Settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Hoppla! Diese Seite ging im Gespräch verloren.\",\n      \"goHomeMessage\": \"Keine Sorge. Du kannst\",\n      \"goHomeLinkText\": \"nach Hause\"\n    },\n    \"room\": {\n      \"speakers\": \"Sprecher\",\n      \"requestingToSpeak\": \"Sprachanfragen\",\n      \"listeners\": \"Zuhörer\",\n      \"allowAll\": \"Alle zulassen\",\n      \"allowAllConfirm\": \"Bist du sicher? Damit erteilst du {{count}} Benutzern Redeberechtigungen.\"\n    },\n    \"searchUser\": { \"search\": \"Suchen...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Ton\",\n      \"title\": \"Toneinstellungen\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profil bearbeiten\",\n      \"followsYou\": \"Folgt dir\",\n      \"following\": \"Abonniert\",\n      \"followers\": \"Abonnenten\",\n      \"followHim\": \"Folgen\",\n      \"followingHim\": \"Folgt bereits\",\n      \"copyProfileUrl\": \"Benutzer-URL kopieren\",\n      \"urlCopied\": \"URL wurde in die Zwischenablage kopiert\",\n      \"unfollow\": \"Entfolgen\",\n      \"about\": \"Über\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Über\",\n        \"rooms\": \"Räume\",\n        \"scheduled\": \"Geplant\",\n        \"recorded\": \"Aufgenommen\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Blockieren\",\n      \"unblock\": \"Freigeben\",\n      \"sendDM\": \"Sende Direktnachricht\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"Dieser Nutzer hat dich blockiert.\",\n        \"default\": \"Hoppla! Wir konnten diesen Benutzer nicht laden.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Spracheinstellungen\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Keine Mikrofone gefunden, es ist entweder keines angeschlosen oder diese Seite besitzt nicht die benötigten Berechtigungen.\",\n      \"volume\": \"Lautstärke:\",\n      \"refresh\": \"Mikrofonliste aktualisieren\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Ungültiger App-Titel\",\n        \"label\": \"Gib den App-Titel ein\"\n      },\n      \"header\": \"Overlay Einstellungen\",\n      \"errorMsg\": \"Bitte gib einen gültigen App-Titel ein\",\n      \"label\": \"App-Titel eingeben\"\n    },\n    \"download\": {\n      \"starting\": \"Starte Download...\",\n      \"failed\": \"Konnte nicht automatisch herunterladen. Bitte versuche es später erneut. \",\n      \"visit_gh\": \"Besuche Github Releases\",\n      \"prompt\": \"Drücke den Button unten, um den Download zu starten.\",\n      \"download_now\": \"Lade herunter\",\n      \"download_for\": \"Download für %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privatsphäre Einstellungen\",\n      \"header\": \"Privatsphäre Einstellungen\",\n      \"whispers\": { \"label\": \"Flüstern\", \"on\": \"An\", \"off\": \"Aus\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Deine Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Informationen\",\n      \"apiKey\": \"API-Key\",\n      \"regenerate\": \"Neugenerieren\",\n      \"reveal\": \"Klicke hier, um den API-Key anzuzeigen\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"addToCalendar\": { \"add\": \"Zum Kalender hinzufügen\" },\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Blockierte Benutzer\",\n      \"unban\": \"Blockierung aufheben\",\n      \"noBans\": \"Du hast aktuell (noch) niemanden blockiert\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Aktuellen Raum verlassen\",\n      \"confirmLeaveRoom\": \"Bist du sicher, dass du gehen möchtest?\",\n      \"leave\": \"Verlassen\",\n      \"inviteUsersToRoomBtn\": \"Benutzer zum Raum einladen\",\n      \"invite\": \"Einladen\",\n      \"toggleMuteMicBtn\": \"Mikrofon ein-/ausschalten\",\n      \"mute\": \"Stumm schalten\",\n      \"unmute\": \"Stummschaltung aufheben\",\n      \"makeRoomPublicBtn\": \"Raum öffentlich machen!\",\n      \"settings\": \"Einstellungen\",\n      \"speaker\": \"Sprecher\",\n      \"listener\": \"Zuhörer\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Audioausgabe ein-/ausschalten\",\n      \"deafen\": \"Audioausgabe\",\n      \"undeafen\": \"Audioausgabe aufheben\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Wir haben keinen Zugriff auf dein Mikrofon. Öffne deine Browsereinstellungen, setze die nötigen Berechtigungen und lade die Seite erneut.\",\n      \"dismiss\": \"Verstanden\",\n      \"tryAgain\": \"Erneut versuchen\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Öffentlich\",\n        \"private\": \"Privat\",\n        \"roomName\": \"Raumname\",\n        \"roomDescription\": \"Raumbeschreibung\",\n        \"descriptionError\": \"Die maximale Länge beträgt 500 Zeichen\",\n        \"nameError\": \"Muss zwischen 2 und 60 Zeichen lang sein\",\n        \"subtitle\": \"Fülle die folgenden Felder aus, um einen Raum zu erstellen\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Neuen Raum erstellt\",\n        \"roomInviteFrom\": \"Neue Einladung von\",\n        \"justStarted\": \"Sie starten gerade\",\n        \"likeToJoin\": \", möchtest du beitreten?\",\n        \"inviteReceived\": \"Du wurdest eingeladen zu\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Benutzername ist bereits vergeben\",\n        \"avatarUrlError\": \"Ungültiges Bild\",\n        \"avatarUrlLabel\": \"GitHub / Twitter Avatar URL\",\n        \"displayNameError\": \"Länge: 2 bis 50 Buchstaben\",\n        \"displayNameLabel\": \"Anzeigename\",\n        \"usernameError\": \"Länge: 4 bis 15 alphanumerische Zeichen und Unterstriche\",\n        \"usernameLabel\": \"Benutzername\",\n        \"bioError\": \"Maximal 160 Buchstaben\",\n        \"bioLabel\": \"Biografie\",\n        \"bannerUrlLabel\": \"Twitter Banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Bist du sicher, dass du diesen Benutzer aus allen deinen zukünftigen Räumen ausschließen möchtest?\",\n        \"blockUser\": \"Benutzer blockieren\",\n        \"makeMod\": \"Zum Moderator befördern\",\n        \"unmod\": \"Moderator Status aufheben\",\n        \"addAsSpeaker\": \"Zu Sprecher befördern\",\n        \"moveToListener\": \"Zu Zuhörer herabstufen\",\n        \"banFromChat\": \"Im Chat blockieren\",\n        \"banFromRoom\": \"Aus Raum ausschließen\",\n        \"goBackToListener\": \"Zurück zum Zuhörer\",\n        \"deleteMessage\": \"Nachricht löschen\",\n        \"makeRoomCreator\": \"Zum Raumadmin befördern\",\n        \"unBanFromChat\": \"Vom Chat entbannen\",\n        \"banIPFromRoom\": \"IP aus Raum bannen\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Berechtigung zum Sprechen verlangen\",\n        \"makePublic\": \"Raum öffentlich machen\",\n        \"makePrivate\": \"Raum privat machen\",\n        \"renamePublic\": \"Öffentlichen Raum-Namen setzen\",\n        \"renamePrivate\": \"Privaten Raum-Namen setzen\",\n        \"chatDisabled\": \"Chat ausschalten\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Aktiviert\",\n          \"disabled\": \"Deaktiviert\",\n          \"followerOnly\": \"Nur Abonnenten\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Nutzername ist bereits vergeben\",\n        \"subtitle\": \"Bitte fülle die angaben unten aus, um deinen Bot zu erstellen\",\n        \"title\": \"Bot Erstellen\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Aus unbekannten Gründen wurde kein Audio Consumer gefunden\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Tastenkürzel setzen\",\n      \"listening\": \"Zuhören\",\n      \"toggleMuteKeybind\": \"Stummschalt-Tastenkürzel umschalten\",\n      \"togglePushToTalkKeybind\": \"Push-to-Talk-Tastenkürzel umschalten\",\n      \"toggleOverlayKeybind\": \"Overlay-Tastenkürzel umschalten\",\n      \"toggleDeafKeybind\": \"Audioausgabe-Tastenkürzel umschalten\"\n    },\n    \"wsKilled\": {\n      \"description\": \"Die Websocket Verbindung zum Server wurde unterbrochen. Dies geschieht normalerweise, wenn du die Website in einem anderen Tab öffnest.\",\n      \"reconnect\": \"Erneut verbinden\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Dein Gerät wird aktuell nicht unterstützt. Du kannst ein\",\n      \"linkText\": \"Issue auf GitHub\",\n      \"addSupport\": \"erstellen und ich werde versuchen, die Unterstützung für dein Gerät hinzuzufügen.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Eingeladen\",\n      \"inviteToRoom\": \"Zum Raum einladen\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Leute\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Aktuell sind keine deiner Freunde online.\",\n      \"showMore\": \"Zeige mehr\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Anstehende Räume\",\n      \"exploreMoreRooms\": \"Finde weitere Räume\"\n    },\n    \"search\": {\n      \"placeholder\": \"Suche nach Räumen, Benutzern oder Kategorien\",\n      \"placeholderShort\": \"Suche\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Sprache\",\n      \"reportABug\": \"Melde einen Fehler\",\n      \"useOldVersion\": \"Benutze alte Version\",\n      \"logOut\": {\n        \"button\": \"Abmelden\",\n        \"modalSubtitle\": \"Möchtest du dich wirklich abmelden?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Ton debuggen\",\n        \"stopDebugger\": \"Stoppe Debugger\"\n      },\n      \"downloadApp\": \"App herunterladen\",\n      \"developer\": \"Entwickler Einstellungen\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Nachrichten\",\n      \"showMore\": \"Zeige mehr\",\n      \"noMessages\": \"Keine neue Nachrichten\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Geplante Räume\",\n      \"noneFound\": \"Nicht gefunden\",\n      \"allRooms\": \"Alle geplanten Räume\",\n      \"myRooms\": \"Meine geplanten Räume\",\n      \"scheduleRoomHeader\": \"Plane einen Raum\",\n      \"startRoom\": \"Raum starten\",\n      \"modal\": {\n        \"needsFuture\": \"Zeitpunkt muss in der Zukunft liegen\",\n        \"roomName\": \"Name des Raums\",\n        \"minLength\": \"minimale Länge: 2\",\n        \"roomDescription\": \"Raumbeschreibung\"\n      },\n      \"tommorow\": \"Morgen\",\n      \"today\": \"Heute\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Möchtest du diesen geplanten Raum wirklich löschen?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[Emotes kommen bald]\",\n      \"bannedAlert\": \"Du wurdest aus dem Chat ausgeschlossen\",\n      \"waitAlert\": \"Du musst eine Sekunde warten, bevor du eine weitere Nachricht schreiben kannst\",\n      \"search\": \"Suchen\",\n      \"searchResults\": \"Suchergebnisse\",\n      \"recent\": \"Häufig verwendet\",\n      \"sendMessage\": \"Eine Nachricht schicken\",\n      \"whisper\": \"Flüstern\",\n      \"welcomeMessage\": \"Willkommen im Chat!\",\n      \"roomDescription\": \"Raumbeschreibung\",\n      \"disabled\": \"Der Chat wurde vom Raum ausgeschaltet\",\n      \"messageDeletion\": {\n        \"message\": \"Nachicht\",\n        \"retracted\": \"zurückgezogen\",\n        \"deleted\": \"gelöscht\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Rakete wird getankt!\",\n      \"takingOff\": \"Am Abheben\",\n      \"inSpace\": \"Im Weltall\",\n      \"approachingMoon\": \"Nähert sich dem Mond!\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Nähert sich der Sonne!\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Nähert sich einer Galaxie!\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet mit Leben gefunden!\"\n    },\n    \"feed\": { \"yourFeed\": \"Dein Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/de-AT/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Zag mehr au\",\n    \"loading\": \"Lona...\",\n    \"noUsersFound\": \"Kane Benutzer gfunden\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Jo\",\n    \"no\": \"Na\",\n    \"cancel\": \"Wü I ned\",\n    \"save\": \"Speichan\",\n    \"edit\": \"Beorbeiten\",\n    \"delete\": \"Weghauen\",\n    \"joinRoom\": \"Raum einigeh\",\n    \"copyLink\": \"Link kopian\",\n    \"copied\": \"kopiart\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Bitte beachte, wennst Dogehouse ohne a Berechtigung rennen lost kennan ungwollte Fehler auftretn.\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Stüh | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ursprungsgschicht\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Problem möden\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"blockian\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Benutzer, denen du foigst, die oba in an privaten Raum san.\",\n      \"currentRoom\": \"Zurzeit in:\",\n      \"startPrivateRoom\": \"An neichn Raum mit dem Benutzer starten\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Raum erstön\",\n      \"refresh\": \"Aktualisiern\",\n      \"editRoom\": \"Raum bearbeiten\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Den Raum kau ma ned finden. Zruck\",\n      \"shareRoomLink\": \"Link zum Raum teilen\",\n      \"inviteFollowers\": \"Du kaust deine Abonnenten, de online san, eilona:\",\n      \"whenFollowersOnline\": \"Wenn deine Abonnenten online san, werden de do auzagt.\"\n    },\n    \"login\": {\n      \"headerText\": \"Wir bringan den Sprochchat zum Mond 🚀\",\n      \"featureText_1\": \"Dark-Mode\",\n      \"featureText_2\": \"Unbeschränkte Registrierung\",\n      \"featureText_3\": \"Plattformunobhängig\",\n      \"featureText_4\": \"Open-Source\",\n      \"featureText_5\": \"Text-Chat\",\n      \"featureText_6\": \"Autrieben durch Doge\",\n      \"loginGithub\": \"mit GitHub aumöden\",\n      \"loginTwitter\": \"mit Twitter aumöden\",\n      \"createTestUser\": \"Testnutzer erstön\",\n      \"loginDiscord\": \"mit Discord aumöden\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Omöden\",\n      \"probablyLoading\": \"Londt vermutlich...\",\n      \"voiceSettings\": \"Sprocheinstöllungen\",\n      \"soundSettings\": \"Toneinstöllungen\",\n      \"deleteAccount\": \"Konto löschn\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Tuat uns leid, konntn den Nutza ned findn\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oida! De Seite ist im Gespräch verlurn gaunga.\",\n      \"goHomeMessage\": \"Ka Sorg. Du kaunst\",\n      \"goHomeLinkText\": \"noch Hause\"\n    },\n    \"room\": {\n      \"speakers\": \"Sprecha\",\n      \"requestingToSpeak\": \"Sprochanfrogen\",\n      \"listeners\": \"Zuhöra\",\n      \"allowAll\": \"Ollen erlaubn\",\n      \"allowAllConfirm\": \"Bist da sicha? Dadurch kennan olle {{count}} anfragenden Bnutzer spröchn\"\n    },\n    \"searchUser\": { \"search\": \"Suachn...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Ton\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profü beorbeiten\",\n      \"followsYou\": \"foigt da\",\n      \"following\": \"Abonniert\",\n      \"followers\": \"Abonnenten\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"Profil URL kopian\",\n      \"urlCopied\": \"URL zua Zwischenablag kopian\",\n      \"unfollow\": \"Entfolgn\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Sprocheinstöllungen\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Kane Mikrofone gfunden, es is entweda kans augschlossen oder de Seitn besitzt ned die benötigten Berechtigungen.\",\n      \"volume\": \"Lautstärkn:\",\n      \"refresh\": \"Mikrofonliste aktualisiern\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Bitte giab an gültign Apptitel au\",\n        \"label\": \"Giab an Apptitel au\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"addToCalendar\": { \"add\": \"Zum Kalenda hinzufügn\" },\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"blockierte Benutzer\",\n      \"unban\": \"Blockierung aufhebn\",\n      \"noBans\": \"Du host aktuell (nu) nermt blockiert\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Aktuellen Raum verlossen\",\n      \"confirmLeaveRoom\": \"Bist da sicher, dass du geh mechst?\",\n      \"leave\": \"Verlossen\",\n      \"inviteUsersToRoomBtn\": \"Benutzer zum Raum eilona\",\n      \"invite\": \"Eilona\",\n      \"toggleMuteMicBtn\": \"Mikrofon ein-/ausschoiten\",\n      \"mute\": \"Stumm schoiten\",\n      \"unmute\": \"Stummschoitung aufhebn\",\n      \"makeRoomPublicBtn\": \"Raum öffentlich mochen!\",\n      \"settings\": \"Einstöllungen\",\n      \"speaker\": \"Sprecha\",\n      \"listener\": \"Zuhörer\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Wir haum kan Zugriff auf dei Mikrofon. Öffne eine Browsereinstöllungen, setz die nötigen Berechtigungen und lod de Seitn neich.\",\n      \"dismiss\": \"verstaunden\",\n      \"tryAgain\": \"erneut versuachen\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Öffentlich\",\n        \"private\": \"Privat\",\n        \"roomName\": \"Raumnaum\",\n        \"roomDescription\": \"Raumbeschreibung\",\n        \"descriptionError\": \"de maximale Länge is 500\",\n        \"nameError\": \"muas zwischen 2 und 60 Zeichn laung sei\",\n        \"subtitle\": \"Füll de folgendn Felder aus um den Raum zu startn\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Neichn Raum erstöt\",\n        \"roomInviteFrom\": \"Neiche Einlodung vo\",\n        \"justStarted\": \"Sie starten grod\",\n        \"likeToJoin\": \", mechast du beitreten?\",\n        \"inviteReceived\": \"Du wurdest einglondt zu\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Benutzernaume is bereits weg\",\n        \"avatarUrlError\": \"ungütigs Büd\",\n        \"avatarUrlLabel\": \"GitHub / Twitter Avatar URL\",\n        \"displayNameError\": \"Läng: 2 bis 50 Buchstauben\",\n        \"displayNameLabel\": \"Anzeigenaume\",\n        \"usernameError\": \"Läng: 4 bis 15 alphanumerische Zeichn und Unterstriche\",\n        \"usernameLabel\": \"Benutzernaume\",\n        \"bioError\": \"Maximal 160 Buchstauben\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Bist da sicher, dass du den Benutzer aus ollen deinen zukünftigen Räumen ausschliaßen mechast?\",\n        \"blockUser\": \"Benutzer blockian\",\n        \"makeMod\": \"Zum Moderator befördern\",\n        \"unmod\": \"Moderator Status aufhebn\",\n        \"addAsSpeaker\": \"zu Sprecha befördern\",\n        \"moveToListener\": \"zu Zuhörer herabstufen\",\n        \"banFromChat\": \"Im Chat blockian\",\n        \"banFromRoom\": \"Aus Raum ausschliaßen\",\n        \"goBackToListener\": \"Zruck zum Zuhörer\",\n        \"deleteMessage\": \"Nochricht löschn\",\n        \"makeRoomCreator\": \"zum Raumadmin befördern\",\n        \"unBanFromChat\": \"Vom Chat entblockian\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Berechtigung zum Sprechen verlaungen\",\n        \"makePublic\": \"Raum öffentlich mochen\",\n        \"makePrivate\": \"Raum privat mochen\",\n        \"renamePublic\": \"Öffentlichn Raumnaumn festlegn\",\n        \"renamePrivate\": \"Privatn Raumnaumn festlegn\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Aus irgendan Grund wurde ka Audio Consumer gfunden\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Tastenkürzel setzen\",\n      \"listening\": \"Zuhören\",\n      \"toggleMuteKeybind\": \"Stummschoittaste umschoiten\",\n      \"togglePushToTalkKeybind\": \"Push-to-Talk Tastenkürzl umschoiten\",\n      \"toggleOverlayKeybind\": \"Overlay-Tastenkürzl umschoiten\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"wsKilled\": {\n      \"description\": \"De Websocket Verbindung zum Server wurde unterbrochen. Des passiert normalerweise, wenn du de Website in an aunderen Tab öffnest.\",\n      \"reconnect\": \"Nur moi verbinden\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Dei Gerät wird aktuell ned unterstützt. Du kaunnst a\",\n      \"linkText\": \"Issue auf GitHub\",\n      \"addSupport\": \"erstön und I werd versuachen, de Unterstützung für dei Gerät hinzuzufügn.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"einglondt\",\n      \"inviteToRoom\": \"zum Raum eilona\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Leit\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Du host grod 0 freind online\",\n      \"showMore\": \"Zoag ma mehr\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Zukünftige Räum\",\n      \"exploreMoreRooms\": \"Erforsch mehr Räum\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Sproch\",\n      \"reportABug\": \"An Fehler möden\",\n      \"useOldVersion\": \"Nutz a oide Version\",\n      \"logOut\": {\n        \"button\": \"Omöden\",\n        \"modalSubtitle\": \"Bist da sicha dost di omöden wüst?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Geplante Räume\",\n      \"noneFound\": \"Ned gefundn\",\n      \"allRooms\": \"Olle geplanten Räume\",\n      \"myRooms\": \"Meine geplanten Räume\",\n      \"scheduleRoomHeader\": \"Plane an Raum\",\n      \"startRoom\": \"Raum starten\",\n      \"modal\": {\n        \"needsFuture\": \"Zeitpunkt muas in da Zukunft liegn\",\n        \"roomName\": \"Naum des Raums\",\n        \"minLength\": \"minimale Läng: 2\",\n        \"roomDescription\": \"Raumbeschreibung\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes boid]\",\n      \"bannedAlert\": \"Du wurdest aus dem Chat ausgeschlossn\",\n      \"waitAlert\": \"Du musst a Sekunde worten, bevor du a weitere Nochricht schreiben kaunst\",\n      \"search\": \"Suachen\",\n      \"searchResults\": \"Suachergebnisse\",\n      \"recent\": \"Oft verwendet\",\n      \"sendMessage\": \"A Nochricht schickn\",\n      \"whisper\": \"Flüstern\",\n      \"welcomeMessage\": \"Servus im Chat!\",\n      \"roomDescription\": \"Raumbeschreibung\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Rakete betankn\",\n      \"takingOff\": \"Auhebn\",\n      \"inSpace\": \"Im Wödall\",\n      \"approachingMoon\": \"Dem Mond aunnähern\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Da Sun aunnähern\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Da Galaxie aunnähern\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet mit Lebn gsichtet\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/el/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Περισσότερα\",\n    \"loading\": \"Φόρτωση...\",\n    \"noUsersFound\": \"Δεν βρέθηκαν χρήστες!\",\n    \"ok\": \"Οκ\",\n    \"yes\": \"Ναι\",\n    \"no\": \"Όχι\",\n    \"cancel\": \"Ακύρωση\",\n    \"save\": \"Αποθήκευση\",\n    \"edit\": \"Επεξεργασία\",\n    \"delete\": \"Διαγραφή\",\n    \"joinRoom\": \"Είσοδος στο δωμάτιο\",\n    \"copyLink\": \"Αντιγραφή συνδέσμου\",\n    \"copied\": \"Αντιγράφτηκε\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Λάβετε υπόψη ότι το να τρέχετε το DogeHouse χωρίς δικαιώματα προσβασιμότητας μπορεί να προκαλέσει ανεπιθύμητα σφάλματα\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Σε σίγαση | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Ταμπλό\",\n    \"connectionTaken\": \"Έγινε σύνδεση\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ιστορία\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Αναφορά Προβλήματος\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Αποκλεισμός\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Xρήστες που ακολουθείτε και δεν βρίσκονται σε κάποιο δωμάτιο\",\n      \"currentRoom\": \"Aυτή τη στιγμή βρίσκεστε σε:\",\n      \"startPrivateRoom\": \"Δημιουργήστε ένα ιδιωτικό δωμάτιο μαζί τους\",\n      \"title\": \"People\"\n    },\n    \"download\": {\n      \"starting\": \"Εκίνηση κατεβάσματως...\",\n      \"failed\": \"Δεν έγινε αυτόματο κατέβασμα, δοκιμάστε αργότερα\",\n      \"visit_gh\": \"Δείτε τις εκδόσεις στο Github\",\n      \"prompt\": \"Πατήστε το κουμπί απο κάτω για να αρχίσει το κατέβασμα\",\n      \"download_now\": \"Κατεβάστε το τώρα\",\n      \"download_for\": \"Κατεβάστε για %platform% (%ext%)\"\n    },\n    \"followList\": {\n      \"followHim\": \"Ακολουθήστε\",\n      \"ακολουθήστε\": \"Ακολουθείτε\",\n      \"followingHim\": \"Ακολουθείτε\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Δημιουργία δωματίου\",\n      \"refresh\": \"Επαναφόρτωση\",\n      \"editRoom\": \"Επεξεργασία δωματίου\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Το δωμάτιο διαγράφηκε, πηγαίνετε πίσω\",\n      \"shareRoomLink\": \"Κοινοποίηση σύνδεσμου δωματίου\",\n      \"inviteFollowers\": \"Μπορείτε να προσκαλέσετε τους ακόλουθους σας που είναι ενεργοί:\",\n      \"whenFollowersOnline\": \"Όταν οι ακόλουθοί σας είναι ενεργοί, θα εμφανίζονται εδώ.\"\n    },\n    \"login\": {\n      \"headerText\": \"Πάμε τις φωνητικές συνομιλίες στο φεγγάρι 🚀\",\n      \"featureText_1\": \"Σκούρο Θέμα\",\n      \"featureText_2\": \"Ανοιχτές Εγγραφές\",\n      \"featureText_3\": \"Υποστήριξη Πολλαπλών Συσκευών\",\n      \"featureText_4\": \"Ανοιχτού Πηγαίου Κώδικα\",\n      \"featureText_5\": \"Συζήτηση Κειμένου\",\n      \"featureText_6\": \"Με την υποστήριξη του Doge\",\n      \"loginGithub\": \"Σύνδεση μέσω GitHub\",\n      \"loginTwitter\": \"Σύνδεση μέσω Twitter\",\n      \"createTestUser\": \"Δημιουργήστε Δοκιμαστικό Χρήστη\",\n      \"loginDiscord\": \"Σύνδεση μέσω Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Αποσύνδεση\",\n      \"probablyLoading\": \"μάλλον φορτώνει...\",\n      \"voiceSettings\": \"Ρυθμίσεις φωνής\",\n      \"soundSettings\": \"Ρυθμίσεις ήχου\",\n      \"deleteAccount\": \"Διαγραφή λογαριασμού\",\n      \"overlaySettings\": \"Πηγαιντε στις ρυθμίσεις του overlay\",\n      \"couldNotFindUser\": \"Συγγνώμη, αυτός ο χρήστης δεν υπάρχει\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ουπς! Αυτή η σελίδα χάθηκε στη συζήτηση.\",\n      \"goHomeMessage\": \"Μην ανησυχείτε. Μπορείτε να\",\n      \"goHomeLinkText\": \"επιστρέψετε στην αρχική σελίδα\"\n    },\n    \"room\": {\n      \"speakers\": \"Ομιλητές\",\n      \"requestingToSpeak\": \"Ζητούν να μιλήσουν\",\n      \"listeners\": \"Ακροατές\",\n      \"allowAll\": \"Να επιτρέπονται όλα\",\n      \"allowAllConfirm\": \"Είστε σίγουροι; Αυτό θα επιτρέψει σε όλους τους {{count}} χρήστες που έχουν ζητήσει, να μιλήσουν\"\n    },\n    \"searchUser\": { \"search\": \"αναζήτηση...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Ήχοι\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Επεξεργασία προφίλ\",\n      \"followsYou\": \"Σας ακολουθεί\",\n      \"followers\": \"ακόλουθοι\",\n      \"following\": \"ακολουθείτε\",\n      \"followHim\": \"Ακολουθήστε\",\n      \"followingHim\": \"Τον ακολουθείτε\",\n      \"copyProfileUrl\": \"Αντιγραφή συνδέσμου προφίλ\",\n      \"urlCopied\": \"Ο συνδεσμος αντιγράφθηκε στο πρόχειρο\",\n      \"unfollow\": \"Ξε-ακολουθήστε\",\n      \"about\": \"Βιογραφικό\",\n      \"bot\": \"Ρομπότ\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ρυθμίσεις Φωνής\",\n      \"mic\": \"Μικρόφωνο:\",\n      \"permissionError\": \"Δεν βρέθηκε μικρόφωνο, είτε δεν έχετε κανένα συνδεδεμένο είτε δεν έχετε δώσει άδεια.\",\n      \"refresh\": \"Ανανέωση λίστας μικροφώνων\",\n      \"volume\": \"Ένταση ήχου:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Μη έγκυρος τίτλος εφαρμογής\",\n        \"label\": \"Εισάγετε τον τίτλο της εφαρμογής\"\n      },\n      \"header\": \"Ρυθμίσεις overlay\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Αποκλεισμένοι Χρήστες\",\n      \"unban\": \"Κατάργηση αποκλεισμού\",\n      \"noBans\": \"Κανένας δεν έχει αποκλειστεί ακόμα\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Αποχώρηση από το παρόν δωμάτιο\",\n      \"confirmLeaveRoom\": \"Είστε σίγουροι ότι θέλετε να αποχωρήσετε;\",\n      \"leave\": \"Αποχώρηση\",\n      \"inviteUsersToRoomBtn\": \"Προσκαλέστε χρήστες στο δωμάτιο\",\n      \"invite\": \"Πρόσκληση\",\n      \"toggleMuteMicBtn\": \"Εναλλαγή κατάστασης μικροφώνου\",\n      \"mute\": \"Σίγαση\",\n      \"unmute\": \"Κατάργηση σίγασης\",\n      \"makeRoomPublicBtn\": \"Κάντε το δωμάτιο δημόσιο\",\n      \"settings\": \"Ρυθμίσεις\",\n      \"speaker\": \"Ομιλητής\",\n      \"listener\": \"Ακροατής\",\n      \"chat\": \"Συζήτηση\",\n      \"toggleDeafMicBtn\": \"Εναλλαγή Κούφανσης\",\n      \"deafen\": \"Κουφαθείτε\",\n      \"undeafen\": \"ξε-κουφαθείτε\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Η συσκευή σας δεν υποστηρίζεται αυτή τη στιγμή. Μπορείτε να δημιουργήσετε ένα\",\n      \"linkText\": \"ζήτημα στο GitHub\",\n      \"addSupport\": \"και θα προσπαθήσουμε να προσθέσουμε υποστήριξη για την συσκευή σας.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Προσκλήθηκαν!\",\n      \"inviteToRoom\": \"Πρόσκληση στο δωμάτιο\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Απορρίφθηκε η άδεια για πρόσβαση στο μικρόφωνό σας (ίσως χρειαστεί να πάτε στις ρυθμίσεις του περιηγητή σας και να κάνετε επαναφόρτωση της σελίδας)\",\n      \"dismiss\": \"Παράλειψη\",\n      \"tryAgain\": \"Προσπαθήστε ξανά\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Αλλαγή συντόμευσης\",\n      \"listening\": \"Ακροατές\",\n      \"toggleMuteKeybind\": \"Συντόμευση εναλλαγής σίγασης\",\n      \"togglePushToTalkKeybind\": \"Συντόμευση εναλλαγής push-to-talk\",\n      \"toggleOverlayKeybind\": \"Συντόμευση για το overlay\",\n      \"toggleDeafKeybind\": \"Συντόμευση για κούφανση\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Δεν υπάρχει δέκτης ήχου για κάποιο λόγο\"\n    },\n    \"addToCalendar\": { \"add\": \"Προσθήκη στο Ημερολόγιο\" },\n    \"wsKilled\": {\n      \"description\": \"Το WebSocket διακόπηκε από τον σέρβερ. Αυτό συμβαίνει συνήθως όταν ανοίγετε την ιστοσελίδα σε άλλη καρτέλα.\",\n      \"reconnect\": \"Επανασύνδεση\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Δημόσιο\",\n        \"private\": \"Ιδιωτικό\",\n        \"roomName\": \"Όνομα δωματίου\",\n        \"roomDescription\": \"Περιγραφή δωματίου\",\n        \"descriptionError\": \"Μέγιστο μήκος 500\",\n        \"nameError\": \"Πρέπει να έχει μήκος μεταξύ 2 και 60 χαρακτήρων\",\n        \"subtitle\": \"Συμπληρώστε τα παρακάτω πεδία για να ξεκινήσετε ένα νέο δωμάτιο\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Δημιουργήθηκε Νέο Δωμάτιο\",\n        \"roomInviteFrom\": \"Πρόσκληση σε Δωμάτιο από\",\n        \"justStarted\": \"Μόλις ξεκίνησαν\",\n        \"likeToJoin\": \", θα θέλατε να συμμετέχετε;\",\n        \"inviteReceived\": \"έχετε προσκληθεί στο\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Όνομα χρήστη μη διαθέσιμο\",\n        \"avatarUrlError\": \"Μη έγκυρη φωτογραφία\",\n        \"avatarUrlLabel\": \"Διεύθυνση URL άβαταρ απο Github/Twitter/Discord\",\n        \"displayNameError\": \"Μήκος από 2 έως 50 χαρακτήρες\",\n        \"displayNameLabel\": \"Εμφανιζόμενο όνομα\",\n        \"usernameError\": \"Μήκος από 4 έως 15 χαρακτήρες και μόνο αλφαριθμητικά/κάτω παύλα\",\n        \"usernameLabel\": \"Όνομα χρήστη\",\n        \"bioError\": \"Μέγιστο μήκος 160 χαρακτήρες\",\n        \"bioLabel\": \"Βιογραφικό\",\n        \"bannerUrlLabel\": \"URL πανό Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Είστε σίγουροι ότι θέλετε να μπλοκάρετε αυτό το χρήστη από το να συμμετέχει σε οποιοδήποτε δωμάτιο δημιουργήσετε στο μέλλον;\",\n        \"blockUser\": \"Μπλοκάρισμα χρήστη\",\n        \"makeMod\": \"Προσθήκη ως συντονιστή\",\n        \"unmod\": \"Αφαίρεση από συντονιστή\",\n        \"addAsSpeaker\": \"Προσθήκη ως ομιλητή\",\n        \"moveToListener\": \"Μετακίνηση σε ακροατή\",\n        \"banFromChat\": \"Αποκλεισμός από τη συζήτηση\",\n        \"banFromRoom\": \"Αποκλεισμός από το δωμάτιο\",\n        \"goBackToListener\": \"Επιστροφή σε ακροατή\",\n        \"deleteMessage\": \"Διαγράψτε αυτό το μήνυμα\",\n        \"makeRoomCreator\": \"Μετατροπή σε διαχειριστή δωματίου\",\n        \"unBanFromChat\": \"Κατάργηση αποκλεισμού από την συζήτηση\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Να απαιτείται άδεια για ομιλία\",\n        \"makePublic\": \"Κάντε το δωμάτιο δημόσιο\",\n        \"makePrivate\": \"Κάντε το δωμάτιο ιδιωτικό\",\n        \"renamePublic\": \"Ορίστε ένα όνομα για το δημόσιο δωμάτιο\",\n        \"renamePrivate\": \"Ορίστε ένα όνομα για το ιδιωτικό δωμάτιο\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Χρήστες\",\n      \"online\": \"Ενεργοί\",\n      \"noOnline\": \"Έχετε 0 ενεργούς φίλους αυτήν την στιγμή\",\n      \"showMore\": \"Περισσότερα\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Επερχόμενα δωμάτια \",\n      \"exploreMoreRooms\": \"Ανακαλύψτε περισσότερα δωμάτια\"\n    },\n    \"search\": {\n      \"placeholder\": \"Αναζητήστε δωμάτια, χρήστες ή κατηγορίες\",\n      \"placeholderShort\": \"Αναζήτηση\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Προφίλ\",\n      \"language\": \"Γλώσσα\",\n      \"reportABug\": \"Αναφορά Προβλήματος\",\n      \"useOldVersion\": \"Χρησιμοποιήστε την παλιά έκδοση\",\n      \"logOut\": {\n        \"button\": \"Αποσύνδεση\",\n        \"modalSubtitle\": \"Είστε σίγουροι πως θέλετε να αποσυνδεθείτε;\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Κάντε debug τον ήχο\",\n        \"stopDebugger\": \"Σταματήστε το Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Προγραμματισμένα δωμάτια\",\n      \"noneFound\": \"Δεν βρέθηκε κανένα\",\n      \"allRooms\": \"Όλα τα προγραμματισμένα δωμάτια\",\n      \"myRooms\": \"Τα προγραμματισμένα δωμάτια μου\",\n      \"scheduleRoomHeader\": \"Προγραμματισμός Δωματίου\",\n      \"startRoom\": \"Έναρξη δωματίου\",\n      \"modal\": {\n        \"needsFuture\": \"Πρέπει να είναι στο μέλλον\",\n        \"roomName\": \"'Ονομα δωματίου\",\n        \"roomDescription\": \"Περιγραφή δωματίου\",\n        \"minLength\": \"Ελάχιστο μήκος 2\"\n      },\n      \"tommorow\": \"Άυριο\",\n      \"today\": \"Σήμερα\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Είστε σίγουρος πως θέλετε να διαγράψετε αυτό το προγραμματισμέν δωμάτιο;\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Συζήτηση\",\n      \"emotesSoon\": \"[τα emotes έρχονται σύντομα]\",\n      \"bannedAlert\": \"Αποκλειστήκατε από τη συζήτηση\",\n      \"waitAlert\": \"Πρέπει να περιμένετε ένα δευτερόλεπτο προτού στείλετε και άλλο μήνυμα\",\n      \"search\": \"Αναζήτηση\",\n      \"searchResults\": \"Αποτελέσματα αναζήτησης\",\n      \"recent\": \"Συχνά χρησιμοποιημένα\",\n      \"sendMessage\": \"Στείλτε ένα Μήνυμα\",\n      \"whisper\": \"Ψίθυρος\",\n      \"welcomeMessage\": \"Καλως ήλθατε στο Τσάτ!\",\n      \"roomDescription\": \"Περιγραφή δωματίου\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Ανεφοδιασμός πυραύλου\",\n      \"takingOff\": \"Απογείωση\",\n      \"inSpace\": \"Στο διάστημα\",\n      \"approachingMoon\": \"Πλησιάζουμε το Φεγγάρι\",\n      \"lunarDoge\": \"Σεληνιακό Doge\",\n      \"approachingSun\": \"Πλησιάζουμε τον Ήλιο\",\n      \"solarDoge\": \"Ηλιακό Doge\",\n      \"approachingGalaxy\": \"Πλησιάζουμε τον Γαλαξία\",\n      \"galacticDoge\": \"Γαλαξιακό Doge\",\n      \"spottedLife\": \"Εντοπίστηκε πλανήτης με ζωή!\"\n    },\n    \"feed\": { \"yourFeed\": \"Η ροή σας\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Load More\",\n    \"loading\": \"Loading...\",\n    \"noUsersFound\": \"No users found!\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"cancel\": \"Cancel\",\n    \"save\": \"Save\",\n    \"edit\": \"Edit\",\n    \"delete\": \"Delete\",\n    \"joinRoom\": \"Join Room\",\n    \"copyLink\": \"Copy Link\",\n    \"copied\": \"Copied!\",\n    \"copy\": \"Copy\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origin Story\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Report a Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    },\n    \"admin\": {\n      \"ban\": \"Ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"download\": {\n      \"prompt\": \"Download the DogeHouse Desktop app\",\n      \"download_for\": \"Download for %platform% (%ext%)\",\n      \"failed\": \"Unable to detect platform, please try again later or visit GitHub Releases\",\n      \"visit_gh\": \"Visit GitHub Releases\"\n    },\n    \"followingOnlineList\": {\n      \"title\": \"People\",\n      \"listHeader\": \"List of users that are not in a private room and you follow.\",\n      \"currentRoom\": \"Currently in:\",\n      \"startPrivateRoom\": \"Start a private room with them\"\n    },\n    \"followList\": {\n      \"title\": \"People\",\n      \"followHim\": \"Follow\",\n      \"followingHim\": \"Following\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"New room\",\n      \"editRoom\": \"Edit Room\",\n      \"refresh\": \"Refresh\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Room gone; go back\",\n      \"shareRoomLink\": \"Share Room Link\",\n      \"inviteFollowers\": \"You can invite your followers that are online:\",\n      \"whenFollowersOnline\": \"When your followers are online, they will show up here.\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Login with GitHub\",\n      \"loginTwitter\": \"Login with Twitter\",\n      \"loginDiscord\": \"Login with Discord\",\n      \"createTestUser\": \"Create Test User\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Logout\",\n      \"probablyLoading\": \"probably loading...\",\n      \"voiceSettings\": \"Voice Settings\",\n      \"overlaySettings\": \"Overlay Settings\",\n      \"soundSettings\": \"Sound Settings\",\n      \"deleteAccount\": \"Delete Account\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy Settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\n      \"goHomeMessage\": \"Not to worry. You can\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": {\n        \"label\": \"Whispers\",\n        \"on\": \"On\",\n        \"off\": \"Off\"\n      }\n    },\n    \"room\": {\n      \"speakers\": \"Speakers\",\n      \"requestingToSpeak\": \"Requesting to Speak\",\n      \"listeners\": \"Listeners\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": {\n      \"search\": \"search...\"\n    },\n    \"soundEffectSettings\": {\n      \"title\": \"Sound Settings\",\n      \"header\": \"Sounds\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Edit Profile\",\n      \"followsYou\": \"Follows you\",\n      \"followers\": \"followers\",\n      \"following\": \"following\",\n      \"followHim\": \"Follow\",\n      \"unfollow\": \"Unfollow\",\n      \"followingHim\": \"Following\",\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"copyProfileUrl\": \"Copy Profile URL\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"aboutSuffix\": \"\",\n      \"bot\": \"Bot\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n\n        \"default\": \"Whoops! We couldn't load this user.\"\n      },\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      }\n    },\n    \"voiceSettings\": {\n      \"title\": \"Voice Settings\",\n      \"header\": \"Voice Settings\",\n      \"mic\": \"Mic:\",\n      \"permissionError\": \"No mics found, you either have none plugged in or haven't given this website permission.\",\n      \"refresh\": \"Refresh Mic List\",\n      \"volume\": \"Volume:\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter a valid app title\",\n        \"label\": \"App title\"\n      }\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Banned Users\",\n      \"unban\": \"Unban\",\n      \"noBans\": \"No one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Leave Current Room\",\n      \"confirmLeaveRoom\": \"Are you sure you want to leave?\",\n      \"leave\": \"Leave\",\n      \"inviteUsersToRoomBtn\": \"Invite Users to Room\",\n      \"invite\": \"Invite\",\n      \"toggleMuteMicBtn\": \"Toggle Mute\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\",\n      \"makeRoomPublicBtn\": \"Make Room Public!\",\n      \"settings\": \"Settings\",\n      \"speaker\": \"Speaker\",\n      \"listener\": \"Listener\",\n      \"chat\": \"Chat\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Your device is currently not supported. You can create an\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"and I will try adding support for your device.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invited!\",\n      \"inviteToRoom\": \"Invite to room\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permission denied trying to access your mic (you may need to go into browser settings and reload the page)\",\n      \"dismiss\": \"Dismiss\",\n      \"tryAgain\": \"Try Again\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Set Keybind\",\n      \"listening\": \"Listening...\",\n      \"toggleMuteKeybind\": \"Toggle mute keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\",\n      \"toggleOverlayKeybind\": \"Toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"Toggle push-to-talk keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"No audio consumer for some reason\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore more rooms\"\n    },\n    \"addToCalendar\": {\n      \"add\": \"Add to Calendar\"\n    },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"Reconnect\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"modals\": {\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      },\n      \"createRoomModal\": {\n        \"subtitle\": \"Fill the following fields to start a new room\",\n        \"public\": \"Public\",\n        \"private\": \"Private\",\n        \"roomName\": \"Room name\",\n        \"roomDescription\": \"Room description\",\n        \"descriptionError\": \"Max length 500\",\n        \"nameError\": \"Must be between 2 to 60 characters long\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"New Room Created\",\n        \"roomInviteFrom\": \"Room Invite from\",\n        \"justStarted\": \"They just started\",\n        \"likeToJoin\": \", would you like to join?\",\n        \"inviteReceived\": \"you've been invited to\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar URL\",\n        \"bannerUrlLabel\": \"Twitter banner URL\",\n        \"displayNameError\": \"Length 2 to 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"Length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"Max length of 160 characters\",\n        \"bioLabel\": \"Bio\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Are you sure you want to block this user from joining any room you ever create?\",\n        \"blockUser\": \"Block user\",\n        \"makeMod\": \"Promote to Mod\",\n        \"unmod\": \"Demote from Mod\",\n        \"addAsSpeaker\": \"Add as Speaker\",\n        \"moveToListener\": \"Move to Listener\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banFromChat\": \"Ban from Chat\",\n        \"banFromRoom\": \"Ban from Room\",\n        \"banIPFromRoom\": \"Ban IP from Room\",\n        \"goBackToListener\": \"Go Back to Listener\",\n        \"deleteMessage\": \"Delete this Message\",\n        \"makeRoomCreator\": \"Promote to Admin\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"require permission to speak\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        },\n        \"makePublic\": \"make room public\",\n        \"makePrivate\": \"make room private\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\"\n      }\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": {\n      \"yourFeed\": \"Your feed\"\n    },\n    \"scheduledRooms\": {\n      \"title\": \"Scheduled Rooms\",\n      \"noneFound\": \"none found\",\n      \"allRooms\": \"all scheduled rooms\",\n      \"myRooms\": \"my scheduled rooms\",\n      \"scheduleRoomHeader\": \"Schedule Room\",\n      \"startRoom\": \"start room\",\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"modal\": {\n        \"needsFuture\": \"needs to be in the future\",\n        \"roomName\": \"room name\",\n        \"roomDescription\": \"Description\",\n        \"minLength\": \"min length 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"You have been banned from chat\",\n      \"waitAlert\": \"You have to wait a second before sending another message\",\n      \"search\": \"Search\",\n      \"searchResults\": \"Search Results\",\n      \"recent\": \"Frequently Used\",\n      \"sendMessage\": \"Send a Message\",\n      \"whisper\": \"Whisper\",\n      \"welcomeMessage\": \"Welcome to chat!\",\n      \"roomDescription\": \"Room description\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-AU/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"ǝɹoɯ pɐoꞁ\",\n    \"loading\": \"˙˙˙ƃuᴉpɐoꞁ\",\n    \"noUsersFound\": \"punoɟ sɹǝsn ou\",\n    \"ok\": \"ʞo\",\n    \"yes\": \"sǝʎ\",\n    \"no\": \"ou\",\n    \"cancel\": \"ꞁǝɔuɐƆ\",\n    \"save\": \"ǝʌɐs\",\n    \"edit\": \"ʇᴉpǝ\",\n    \"delete\": \"ǝʇǝꞁǝp\",\n    \"joinRoom\": \"ɯooɹ uᴉoɾ\",\n    \"copyLink\": \"ʞuᴉꞁ ʎdoɔ\",\n    \"copied\": \"pǝᴉdoɔ\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"sɹoɹɹǝ pǝʇuɐʍun ǝsnɐɔ ʎɐɯ suoᴉssᴉɯɹǝd ʎʇᴉꞁᴉqᴉssǝɔɔɐ ʇnoɥʇᴉʍ ǝsnoHǝƃoᗡ ƃuᴉuunɹ ǝʇou ǝsɐǝꞁԀ\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"ǝsnoHǝƃoᗡ\",\n    \"mutedTitle\": \"ǝsnoHǝƃoᗡ | pǝʇnW\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"ʎɹoʇS uᴉƃᴉɹO\",\n    \"link_2\": \"pɹoɔsᴉᗡ\",\n    \"link_3\": \"ƃnᗺ ɐ ʇɹodǝᴚ\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"uɐq\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"˙ʍoꞁꞁoɟ noʎ puɐ ɯooɹ ǝʇɐʌᴉɹd ɐ uᴉ ʇou ǝɹɐ ʇɐɥʇ sɹǝsn ɟo ʇsᴉꞀ\",\n      \"currentRoom\": \":uᴉ ʎꞁʇuǝɹɹnɔ\",\n      \"startPrivateRoom\": \"ɯǝɥʇ ɥʇᴉʍ ɯooɹ ǝʇɐʌᴉɹd ɐ ʇɹɐʇs\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"ʍoꞁꞁoɟ\",\n      \"followingHim\": \"ƃuᴉʍoꞁꞁoɟ\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"ɯooᴚ ǝʇɐǝɹƆ\",\n      \"refresh\": \"ɥsǝɹɟǝᴚ\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ʞɔɐq oƃ 'ǝuoƃ ɯooɹ\",\n      \"shareRoomLink\": \"ɯooɹ oʇ ʞuᴉꞁ ǝɹɐɥs\",\n      \"inviteFollowers\": \":ǝuᴉꞁuo ǝɹɐ ʇɐɥʇ sɹǝʍoꞁꞁoɟ ɹnoʎ ǝʇᴉʌuᴉ uɐɔ no⅄\",\n      \"whenFollowersOnline\": \"˙ǝɹǝɥ dn ʍoɥs ꞁꞁᴉʍ ʎǝɥʇ 'ǝuᴉꞁuo ǝɹɐ sɹǝʍoꞁꞁoɟ ɹnoʎ uǝɥM\"\n    },\n    \"login\": {\n      \"headerText\": \"🚀 uooɯ ǝɥʇ oʇ suoᴉʇɐsɹǝʌuoɔ ǝɔᴉoʌ ƃuᴉʞɐ⊥\",\n      \"featureText_1\": \"ǝɯǝɥ⊥ ʞɹɐᗡ\",\n      \"featureText_2\": \"sd∩-uƃᴉS uǝdO\",\n      \"featureText_3\": \"ʇɹoddnS ɯɹoɟʇɐꞁԀ-ssoɹƆ\",\n      \"featureText_4\": \"ǝɔɹnoS uǝdO\",\n      \"featureText_5\": \"ʇɐɥƆ ʇxǝ⊥\",\n      \"featureText_6\": \"ǝƃoᗡ ʎq pǝɹǝʍoԀ\",\n      \"loginGithub\": \"qnHʇᴉ⅁ ɥʇᴉʍ uᴉƃoꞁ\",\n      \"loginTwitter\": \"ɹǝʇʇᴉʍ⊥ ɥʇᴉʍ uᴉƃoꞁ\",\n      \"createTestUser\": \"ɹǝsn ʇsǝʇ ǝʇɐǝɹɔ\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"ʇnoƃoꞁ\",\n      \"probablyLoading\": \"˙˙˙ƃuᴉpɐoꞁ ʎꞁqɐqoɹd\",\n      \"voiceSettings\": \"sƃuᴉʇʇǝs ǝɔᴉoʌ oʇ oƃ\",\n      \"overlaySettings\": \"sƃuᴉʇʇǝs ʎɐꞁɹǝʌo oʇ oƃ\",\n      \"soundSettings\": \"sƃuᴉʇʇǝs punos oʇ oƃ\",\n      \"deleteAccount\": \"ʇunoɔɔɐ ǝʇǝꞁǝp\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"˙uoᴉʇɐsɹǝʌuoɔ uᴉ ʇsoꞁ ʇoƃ ǝƃɐd sᴉɥ⊥ ¡sdooɥM\",\n      \"goHomeMessage\": \"uɐɔ no⅄ ˙ʎɹɹoʍ oʇ ʇoN\",\n      \"goHomeLinkText\": \"ǝɯoɥ oƃ\"\n    },\n    \"room\": {\n      \"speakers\": \"sɹǝʞɐǝdS\",\n      \"requestingToSpeak\": \"ʞɐǝds oʇ ƃuᴉʇsǝnbǝᴚ\",\n      \"listeners\": \"sɹǝuǝʇsᴉꞀ\",\n      \"allowAll\": \"ꞁꞁɐ ʍoꞁꞁⱯ\",\n      \"allowAllConfirm\": \"ʞɐǝds oʇ sɹǝsn ƃuᴉʇsǝnbǝɹ {{count}} ꞁꞁɐ ʍoꞁꞁɐ ꞁꞁᴉʍ sᴉɥ⊥ ¿ǝɹns noʎ ǝɹⱯ\"\n    },\n    \"searchUser\": { \"search\": \"˙˙˙ɥɔɹɐǝs\" },\n    \"soundEffectSettings\": {\n      \"header\": \"spunoS\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"ǝꞁᴉɟoɹd ʇᴉpǝ\",\n      \"followsYou\": \"noʎ sʍoꞁꞁoɟ\",\n      \"followers\": \"sɹǝʍoꞁꞁoɟ\",\n      \"following\": \"ƃuᴉʍoꞁꞁoɟ\",\n      \"followHim\": \"ʍoꞁꞁoɟ\",\n      \"followingHim\": \"ƃuᴉʍoꞁꞁoɟ\",\n      \"copyProfileUrl\": \"ꞁɹn ǝꞁᴉɟoɹd ʎdoɔ\",\n      \"urlCopied\": \"pɹɐoqdᴉꞁɔ oʇ pǝᴉdoɔ Ꞁᴚ∩\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"sƃuᴉʇʇǝS ǝɔᴉoɅ\",\n      \"mic\": \":ɔᴉɯ\",\n      \"permissionError\": \"˙uoᴉssᴉɯɹǝd ǝʇᴉsqǝʍ sᴉɥʇ uǝʌᴉƃ ʇ,uǝʌɐɥ ɹo uᴉ pǝƃƃnꞁd ǝuou ǝʌɐɥ ɹǝɥʇᴉǝ noʎ 'punoɟ sɔᴉɯ ou\",\n      \"refresh\": \"ʇsᴉꞁ ɔᴉɯ ɥsǝɹɟǝɹ\",\n      \"volume\": \":ǝɯnꞁoʌ\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"sƃuᴉʇʇǝS ʎɐꞁɹǝʌO\",\n      \"input\": {\n        \"errorMsg\": \"ǝꞁʇᴉʇ ddɐ pᴉꞁɐʌ ɹǝʇuǝ ǝsɐǝꞁԀ\",\n        \"label\": \"ǝꞁʇᴉʇ ddɐ ɹǝʇuƎ\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"sɹǝs∩ pǝuuɐᗺ\",\n      \"unban\": \"uɐqun\",\n      \"noBans\": \"ʇǝʎ pǝuuɐq uǝǝq sɐɥ ǝuo ou\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"ɯooɹ ʇuǝɹɹnɔ ǝʌɐǝꞀ\",\n      \"confirmLeaveRoom\": \"¿ǝʌɐǝꞁ oʇ ʇuɐʍ noʎ ǝɹns noʎ ǝɹⱯ\",\n      \"leave\": \"ǝʌɐǝꞀ\",\n      \"inviteUsersToRoomBtn\": \"ɯooɹ oʇ sɹǝsn ǝʇᴉʌuI\",\n      \"invite\": \"ǝʇᴉʌuI\",\n      \"toggleMuteMicBtn\": \"ǝuoɥdoɹɔᴉɯ ǝʇnɯ ǝꞁƃƃo⊥\",\n      \"mute\": \"ǝʇnW\",\n      \"unmute\": \"ǝʇnɯu∩\",\n      \"makeRoomPublicBtn\": \"¡ɔᴉꞁqnd ɯooɹ ǝʞɐW\",\n      \"settings\": \"sƃuᴉʇʇǝS\",\n      \"speaker\": \"ɹǝʞɐǝdS\",\n      \"listener\": \"ɹǝuǝʇsᴉꞀ\",\n      \"chat\": \"ʇɐɥƆ\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"uɐ ǝʇɐǝɹɔ uɐɔ no⅄ ˙pǝʇɹoddns ʇou ʎꞁʇuǝɹɹnɔ sᴉ ǝɔᴉʌǝp ɹno⅄\",\n      \"linkText\": \"qnHʇᴉ⅁ uo ǝnssᴉ\",\n      \"addSupport\": \"˙ǝɔᴉʌǝp ɹnoʎ ɹoɟ ʇɹoddns ƃuᴉppɐ ʎɹʇ ꞁꞁᴉʍ I puɐ\"\n    },\n    \"inviteButton\": { \"invited\": \"pǝʇᴉʌuᴉ\", \"inviteToRoom\": \"ɯooɹ oʇ ǝʇᴉʌuᴉ\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"(ǝƃɐd ǝɥʇ pɐoꞁǝɹ puɐ sƃuᴉʇʇǝs ɹǝsʍoɹq oʇuᴉ oƃ oʇ pǝǝu ʎɐɯ noʎ) ɔᴉɯ ɹnoʎ ssǝɔɔɐ oʇ ƃuᴉʎɹʇ pǝᴉuǝp uoᴉssᴉɯɹǝԀ\",\n      \"dismiss\": \"ssᴉɯsᴉp\",\n      \"tryAgain\": \"uᴉɐƃɐ ʎɹʇ\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"puᴉqʎǝʞ ʇǝs\",\n      \"listening\": \"ƃuᴉuǝʇsᴉꞁ\",\n      \"toggleMuteKeybind\": \"puᴉqʎǝʞ ǝʇnɯ ǝꞁƃƃoʇ\",\n      \"toggleOverlayKeybind\": \"puᴉqʎǝʞ ʎɐꞁɹǝʌo ǝꞁƃƃoʇ\",\n      \"togglePushToTalkKeybind\": \"puᴉqʎǝʞ ʞꞁɐʇ-oʇ-ɥsnd ǝꞁƃƃoʇ\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"uosɐǝɹ ǝɯos ɹoɟ ɹǝɯnsuoɔ oᴉpnɐ ou\"\n    },\n    \"addToCalendar\": { \"add\": \"ɹɐpuǝꞁɐƆ oʇ ppⱯ\" },\n    \"wsKilled\": {\n      \"description\": \"˙qɐʇ ɹǝɥʇouɐ uᴉ ǝʇᴉsqǝʍ ǝɥʇ uǝdo noʎ uǝɥʍ suǝddɐɥ ʎꞁꞁɐnsn sᴉɥ⊥ ˙ɹǝʌɹǝs ǝɥʇ ʎq pǝꞁꞁᴉʞ sɐʍ ʇǝʞɔoSqǝM\",\n      \"reconnect\": \"ʇɔǝuuoɔǝɹ\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"ɔᴉꞁqnԀ\",\n        \"private\": \"ǝʇɐʌᴉɹԀ\",\n        \"roomName\": \"ǝɯɐu ɯooᴚ\",\n        \"roomDescription\": \"uoᴉʇdᴉɹɔsǝp ɯooᴚ\",\n        \"descriptionError\": \"005 ɥʇƃuǝꞁ xɐɯ\",\n        \"nameError\": \"ƃuoꞁ sɹǝʇɔɐɹɐɥɔ 09 oʇ ᘔ uǝǝʍʇǝq ǝq ʇsnɯ\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"pǝʇɐǝɹƆ ɯooᴚ ʍǝN\",\n        \"roomInviteFrom\": \"ɯoɹɟ ǝʇᴉʌuI ɯooᴚ\",\n        \"justStarted\": \"pǝʇɹɐʇs ʇsnɾ ʎǝɥ⊥\",\n        \"likeToJoin\": \"¿uᴉoɾ oʇ ǝʞᴉꞁ noʎ pꞁnoʍ '\",\n        \"inviteReceived\": \"oʇ pǝʇᴉʌuᴉ uǝǝq ǝʌ,noʎ\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"uǝʞɐʇ ǝɯɐuɹǝsn\",\n        \"avatarUrlError\": \"ǝƃɐɯᴉ pᴉꞁɐʌuI\",\n        \"avatarUrlLabel\": \"ꞁɹn ɹɐʇɐʌɐ ɹǝʇʇᴉʍ⊥/qnHʇᴉ⅁\",\n        \"displayNameError\": \"sɹǝʇɔɐɹɐɥɔ 05 oʇ ᘔ ɥʇƃuǝꞁ\",\n        \"displayNameLabel\": \"ǝɯɐN ʎɐꞁdsᴉᗡ\",\n        \"usernameError\": \"ǝɹoɔsɹǝpun/ɔᴉɹǝɯnuɐɥdꞁɐ ʎꞁuo puɐ sɹǝʇɔɐɹɐɥɔ 5І oʇ ᔭ ɥʇƃuǝꞁ\",\n        \"usernameLabel\": \"ǝɯɐuɹǝs∩\",\n        \"bioError\": \"sɹǝʇɔɐɹɐɥɔ 09І ɟo ɥʇƃuǝꞁ xɐɯ\",\n        \"bioLabel\": \"oᴉᗺ\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"¿ǝʇɐǝɹɔ ɹǝʌǝ noʎ ɯooɹ ʎuɐ ƃuᴉuᴉoɾ ɯoɹɟ ɹǝsn sᴉɥʇ ʞɔoꞁq oʇ ʇuɐʍ noʎ ǝɹns noʎ ǝɹⱯ\",\n        \"blockUser\": \"ɹǝsn ʞɔoꞁq\",\n        \"makeMod\": \"poɯ ǝʞɐɯ\",\n        \"unmod\": \"poɯun\",\n        \"addAsSpeaker\": \"ɹǝʞɐǝds sɐ ppɐ\",\n        \"moveToListener\": \"ɹǝuǝʇsᴉꞁ oʇ ǝʌoɯ\",\n        \"banFromChat\": \"ʇɐɥɔ ɯoɹɟ uɐq\",\n        \"banFromRoom\": \"ɯooɹ ɯoɹɟ uɐq\",\n        \"goBackToListener\": \"ɹǝuǝʇsᴉꞁ oʇ ʞɔɐq oƃ\",\n        \"deleteMessage\": \"ǝƃɐssǝɯ sᴉɥʇ ǝʇǝꞁǝp\",\n        \"makeRoomCreator\": \"uᴉɯpɐ ɯooɹ ǝʞɐɯ\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"ʞɐǝds oʇ uoᴉssᴉɯɹǝd ǝɹᴉnbǝɹ\",\n        \"makePublic\": \"ɔᴉꞁqnd ɯooɹ ǝʞɐɯ\",\n        \"makePrivate\": \"ǝʇɐʌᴉɹd ɯooɹ ǝʞɐɯ\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"sɯooᴚ pǝꞁnpǝɥɔS\",\n      \"noneFound\": \"punoɟ ǝuou\",\n      \"allRooms\": \"sɯooɹ pǝꞁnpǝɥɔs ꞁꞁɐ\",\n      \"myRooms\": \"sɯooɹ pǝꞁnpǝɥɔs ʎɯ\",\n      \"scheduleRoomHeader\": \"ɯooᴚ ǝꞁnpǝɥɔS\",\n      \"startRoom\": \"ɯooɹ ʇɹɐʇs\",\n      \"modal\": {\n        \"needsFuture\": \"ǝɹnʇnɟ ǝɥʇ uᴉ ǝq oʇ spǝǝu\",\n        \"roomName\": \"ǝɯɐu ɯooɹ\",\n        \"roomDescription\": \"uoᴉʇdᴉɹɔsǝᗡ\",\n        \"minLength\": \"ᘔ ɥʇƃuǝꞁ uᴉɯ\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"ʇɐɥƆ\",\n      \"emotesSoon\": \"[uoos sǝʇoɯǝ]\",\n      \"bannedAlert\": \"ʇɐɥɔ ɯoɹɟ pǝuuɐq ʇoƃ no⅄\",\n      \"waitAlert\": \"ǝƃɐssǝɯ ɹǝɥʇouɐ ƃuᴉpuǝs ǝɹoɟǝq puoɔǝs ɐ ʇᴉɐʍ oʇ ǝʌɐɥ no⅄\",\n      \"search\": \"ɥɔɹɐǝS\",\n      \"searchResults\": \"sʇꞁnsǝᴚ ɥɔɹɐǝS\",\n      \"recent\": \"pǝs∩ ʎꞁʇuǝnbǝɹᖵ\",\n      \"sendMessage\": \"ǝƃɐssǝW ɐ puǝS\",\n      \"whisper\": \"ɹǝdsᴉɥM\",\n      \"welcomeMessage\": \"¡ʇɐɥɔ oʇ ǝɯoɔꞁǝM\",\n      \"roomDescription\": \"uoᴉʇdᴉɹɔsǝp ɯooɹ\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"ʇǝʞɔoɹ ƃuᴉꞁǝnᖵ\",\n      \"takingOff\": \"ɟɟo ƃuᴉʞɐ⊥\",\n      \"inSpace\": \"ǝɔɐds uI\",\n      \"approachingMoon\": \"uooɯ ƃuᴉɥɔɐoɹddⱯ\",\n      \"lunarDoge\": \"ǝƃoᗡ ɹɐunꞀ\",\n      \"approachingSun\": \"uns ƃuᴉɥɔɐoɹddⱯ\",\n      \"solarDoge\": \"ǝƃoᗡ ɹɐꞁoS\",\n      \"approachingGalaxy\": \"ʎxɐꞁɐƃ ƃuᴉɥɔɐoɹddⱯ\",\n      \"galacticDoge\": \"ǝƃoᗡ ɔᴉʇɔɐꞁɐ⅁\",\n      \"spottedLife\": \"pǝʇʇods ǝɟᴉꞁ ɥʇᴉʍ ʇǝuɐꞁԀ\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-C/GUIDE.md",
    "content": "# C'z Iŋglis̈ Alfubet (C's English Alphabet)\n### Wï? (Why?)\nI'll explain, I swear.... later.\n\nFor now, just know that this has been 3 years in the making, with help from many many many (mostly either unwilling or mocking) people.  If adopted, I predict the effective end of illiteracy. We learn the alphabet in Kindergarten, and then spend the next 12 years of school having to learn why it doesn't work.  What if we threw off the shackles of our Roman/Latin oppressors and created an actual English alphabet, made for our language?  What if in Kindergarten, you learned the alphabet, and then you could just read?\n\n**\"That's Not Realistic.  You can't just change the alphabet.\"**\n\nOh ya? [The Turkish did it](https://en.wikipedia.org/wiki/Turkish_alphabet#Introduction_of_the_modern_Turkish_alphabet) when they replaced the Arabic alphabet with their own Turkish one! Granted, they had the help of being a dictatorship....  Ok, how about [Canada's switch to the metric system](https://en.wikipedia.org/wiki/Metrication_in_Canada)?  It could happen like that.\n\n### Þë Alfubet (The Alphabet)\nThe table below shows all the differences between the Roman/Latin alphabet you are currently indoctrinated into using, and Mr C's new and improved English Alphabet.  Most consonants are the same, with a couple exceptions.  Five new consonants and one new vowel are required.\n\n**Character (name)**  | **Sound** | **Notes and Examples** | **Code**\n--- | --- | --- | ---\n |  |  |  |\n**Basic Consonants** | | Each letter only makes ONE sound. That means we have to fix up some problems with the current alphabet... |\nb, d, f, h, j, k, l, m, n, o, p, r, t, v, w, z | Just like you learned in Kindergarten. | These letters haven't changed.  They work pretty well as is, and almost always make the same sound (with a few annoying exceptions) so we'll keep them. |\nc (doesn't exist anymore) | N/A | C has been cancelled. C is redundant. If it makes an 's' sound then use s instead, if it makes a 'k' sound use K instead. Bye bye C. |\ng | ONLY sounds like the hard 'g' in gum. | Don't use the letter 'g' when it sounds like a 'j'.  Use J instead: rigid -> rijid |\nq (doesn't exist anymore) | N/A | Q has been cancelled.  Q is redundant.  Use K instead.  Bye bye Q. |\ns | ONLY sounds like 's' in **s**ee. | Don't use the letter 's' when it sounds like a 'z'.  Use z instead: has -> haz |\ny | Sounds like: 'y' in **y**es, the beginning of the 'u' in **u**se | No more crazy fancy uses of Y.  Examples: use -> yüz (huh? explained more below) |\nx (doesn't exist anymore) | N/A | X has been cancelled, use 'ks' instead, or use Z if it makes a 'z' sound: ax -> aks |\n |  |  |  |\n**Short Vowels** | | Short vowel sounds just use the normal vowel letters: a, e, i, o & u. | See instructions below for how to type special characters using this code\na | The normal \"short vowel\" sound that \"a\" makes.  Sounds like: the 'a' in p**a**t | Just like you learned in Kindergarten. | -\ne | The normal \"short vowel\" sound that \"e\" makes.  Sounds like: the 'e' in p**e**t | Just like you learned in Kindergarten. | -\ni | The normal \"short vowel\" sound that \"i\" makes.  Sounds like: the 'i' in p**i**t | Just like you learned in Kindergarten. | -\no | The normal \"short vowel\" sound that \"o\" makes.  Sounds like: the 'o' in p**o**t | Just like you learned in Kindergarten.  This is actually the hardest letter to use, since you brain is so used to pronouncing 'o' in so many different ways in so many different contexts. From now on, it just says 'o' like in pot! | -\nu | The normal \"short vowel\" sound that \"u\" makes.  Sounds like: the 'u' in p**u**tt (WHY are there two \"t\"s in this word? it makes no sense) | Just like you learned in Kindergarten. | -\n**ə** (schwa) | The \"unstressed vowel\" sound.  Sounds like: the 'u' in p**u**t, the 2nd 'e' in carp**e**t, the 'ea' in l**ea**rn, the 'io' in dict**io**nary | The most common vowel sound in English doesn't actually have its own letter. Instead of it's own letter, English just throws a random vowel or combo of vowels and we have to guess how to pronounce it! | *u*0259\n |  |  |  |\n**Long Vowels** | | Long vowels are when a vowel says it's own name (except 'u'). To differentiate long vowels from short vowels we had a line or double dots over the vowel. | Type the letter first, then use the code to add double dots over it (see below table for instructions)\nä | Sounds like: 'a' in h**a**te, and 'ei' in **ei**ght | Examples: hate -> hät, eight -> ät | A + *u*0308\në | Sounds like: 'e' in h**e**, 'ea' in m**ea**t, 'ee' in cr**ee**p, 'ie' in f**ie**ld, 'y' in an**y**, and 'e' in **e**vil | Examples: he -> hë, meat -> mët, creep -> krëp, field -> fëld, evil -> ëvil, (if you enunciate the 'i', otherwise it would be ëvəl) | E + *u*0308\nï | Sounds like: 'i' in k**i**te, and 'y' in cr**y** | Examples: kite -> kït, cry -> krï | I + *u*0308\nö | Sounds like the 'o' in r**o**pe | This one is pretty easy. Example: poke -> pök | O + *u*0308\nü | Sounds like: 'u' in d**u**ty, 'oe' in sh**oe**, 'oo' in g**oo**p, and 'ew' in st**ew** | This is the only long vowel that doesn't actually say it's own name. It's like the name of the letter \"u\" with the 'y' sound removed form the beginning. Examples: poo -> pü, who -> hü | U + *u*0308\n |  |  |  |\n**New Characters** | | The best part, where we get to introduce NEW characters into the alphabet so we can write sounds that don't have their own letter in the Roman/Latin alphabet! |\ns̈ | Sounds like: 'sh' in a**sh** | We need a letter for the 'sh' sound, so let's just add some dots above an 's'! Examples: ash -> as̈, she -> s̈ë | S + *u*0308\nc or c̈ | Sounds like: 'ch' in **ch**eese | Woah woah woah. I though the C was cancelled? Well, it's still on the keyboard so we might as well use it, so lets give it the 'ch' sound. To prevent confusing your brain, let's put double dots over it to make it consistent with the 'sh' character: chick -> c̈ik,cheese -> c̈ëz, church -> c̈ərc̈ | C + *u*0308\nz̈ | Sounds like 'zh': 's' in mea**s**ure , 'z' in sei**z**ure, 'ti' in equa**ti**on, and 'g' in gara**g**e | This is one of the greatest examples of the failure of the Roman/Latin alphabet to capture the English language.  There are a million ways that we spell the 'zh' sound, and none of them actually use 'zh' !?!!?  Examples: measure -> mez̈ər, seizure -> sëz̈ər, equation -> ëkwäz̈ən, garage -> guroz̈ (remember 'o' like in p**o**t) | Z + *u*0308\n**þ** (thorn) | The voiced 'th' sound: 'th' in **th**en, 'th' in fea**th**er, and 'th' in brea**th**e | Thorn, þ, has a [cool history](https://www.mentalfloss.com/article/31904/12-letters-didnt-make-alphabet#file-306119) and actually was a letter used in Old and Middle English. Examples: then -> þen, feather -> feþər, breathe -> brëþ | *u*00FE\n**ð** (eth) | The voiceless 'th' sound: 'th' in **th**in, 'th' in ba**th**tub, 'th' in ten**th** | Say \"this\" and \"thing\" and listen to what sound the 'th' makes.  They make different sounds, so they need different letters! Another Old English letter with a [neat history](https://www.mentalfloss.com/article/31904/12-letters-didnt-make-alphabet#file-306124) that was tossed out around the year 1000.  Well, it's back baby!! Examples: thin -> ðin, bathtub -> baðtub, tenth -> tenð | *u*00F0\n**ŋ** (eng) | Sounds like: 'ng' in si**ng**, 'ng' in po**ng**, and 'n' in ri**n**k | Like an 'n' with a 'j' tail. Also has a [cool history](https://www.mentalfloss.com/article/31904/12-letters-didnt-make-alphabet#file-306130). Examples: sing -> siŋ, pong -> poŋ, rink -> riŋk | *u*014B\n\n### Tïpəŋ Spes̈əl Kerəktərz (Typing Special Characters)\nTo type special characters in Ubuntu (and many other Linux distros) you can use the special code `Ctrl` + `Shift` + `U` (which will appear as an underlined *u*) then type the four digit code for the character (e.g. 0259), then hit `Space` or `Enter` and the *u* will turn into the character for that code.\n\n\nFor example, to type thorn: **þ**, you would type this combo of keys:\n\n`Ctrl` + `Shift` + `U`, `0`, `0`, `F`, `E`, `Space`\n\nTo create a long vowel with two dots, or a line above it, type the vowel, then type the code combo for the symbol as described above.  The line or dots will appear above the letter.\n\nFor example, to type **ä**, you would type:\n`A`, `Ctrl` + `Shift` + `U`, `0`, `0`, `F`, `E`, `Space`\n\n\n### Fïnul Tip (Final Tip).\n\nDon't know how to spell something? It's spelled just as it sounds, no funny business.\n"
  },
  {
    "path": "kibbeh/public/locales/en-C/translation.json",
    "content": "{\n  \"_comment\": \"to contribute to this you can see the translation guide in the /en-C directory.\",\n  \"common\": {\n    \"loadMore\": \"Löd Mör\",\n    \"loading\": \"Lödiŋ...\",\n    \"noUsersFound\": \"Nö yüzrz fawnd!\",\n    \"ok\": \"Ök\",\n    \"yes\": \"Yes\",\n    \"no\": \"Nö\",\n    \"cancel\": \"Kansəl\",\n    \"save\": \"Säv\",\n    \"edit\": \"Edit\",\n    \"delete\": \"Dəlët\",\n    \"joinRoom\": \"Jöën Rüm\",\n    \"copyLink\": \"Kopë Link\",\n    \"copied\": \"Kopëd!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Plëz nöt runiŋ DogeHouse wiðawt aksesibilitë pərmis̈onz mä koz unwontəd erərz\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Män Hedər UI Intərnas̈ənəlïzäs̈un Striŋz\",\n    \"title\": \"DogeHouse\",\n    \"dashboard\": \"Das̈börd\",\n    \"connectionTaken\": \"Koneks̈un Täkən\",\n    \"mutedTitle\": \"Myütəd | DogeHouse\",\n    \"deafenedTitle\": \"Defənd | DogeHouse\"\n  },\n  \"footer\": {\n    \"_comment\": \"Män Fətər UI Intərnas̈ənəlïzäs̈un Striŋz\",\n    \"link_1\": \"Örijin Störë\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Rëpört ä Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Rëspektiv Päj UI Intərnas̈ənəlïzäs̈un Striŋz\",\n    \"admin\": {\n      \"ban\": \"Ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"download\": {\n      \"prompt\": \"Dawnlöd þə DogeHouse Desktop ap\",\n      \"download_for\": \"Dawnlöd för %platform% (%ext%)\",\n      \"failed\": \"Unäbəl tü dətekt platförm, plëz trï ägen lätər ör vizit GitHub Rëlësəz\",\n      \"visit_gh\": \"Vizit GitHub Rëlësez\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"List uv yüzərz þat ar not in ä prïvit rüm and yü folö.\",\n      \"currentRoom\": \"Kərəntlë in:\",\n      \"startPrivateRoom\": \"Stort ä prïvit rüm wið þem\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Folö\",\n      \"followingHim\": \"Folöiŋ\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Nü̈ Rüm\",\n      \"editRoom\": \"Edit Rüm\",\n      \"refresh\": \"Rëfres̈\",\n      \"desktopAlert\": \"Dawnlöd þə DogeHouse desktop ap tüdä!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Rüm gon; gö bak\",\n      \"shareRoomLink\": \"S̈är Rüm Link\",\n      \"inviteFollowers\": \"Yü kan invït yor folöərz þat or onlïn:\",\n      \"whenFollowersOnline\": \"Wen yör folöərz or onlïn, þä wil s̈ö up hër.\"\n    },\n    \"login\": {\n      \"headerText\": \"Täkiŋ vöës konvrsäs̈onz tü þə mün 🚀\",\n      \"featureText_1\": \"Dərk ðëm\",\n      \"featureText_2\": \"Öpen Sïn-əps\",\n      \"featureText_3\": \"Kros-Platförm Səport\",\n      \"featureText_4\": \"Öpen Sörs\",\n      \"featureText_5\": \"Tekst C̈at\",\n      \"featureText_6\": \"Pawrd bï Döj\",\n      \"loginGithub\": \"Login wið GitHub\",\n      \"loginTwitter\": \"Login wið Twitter\",\n      \"loginDiscord\": \"Login wið Discord\",\n      \"createTestUser\": \"Crëä̈t Test Yüzr\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Logawt\",\n      \"probablyLoading\": \"probäblë lödiŋ...\",\n      \"voiceSettings\": \"Vöës Setiŋz\",\n      \"overlaySettings\": \"Övrlä Setiŋz\",\n      \"soundSettings\": \"Sawnd Setiŋz\",\n      \"deleteAccount\": \"Dlët Akawnt\",\n      \"couldNotFindUser\": \"Sorë, wë kud not fïnd þat yüzr\",\n      \"privacySettings\": \"Prïvasë Setiŋz\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Wəps! Þis päj got lost in konvrsäs̈on.\",\n      \"goHomeMessage\": \"Not tü wrë. Yü kan\",\n      \"goHomeLinkText\": \"gö höm\"\n    },\n    \"room\": {\n      \"speakers\": \"Spëkrz\",\n      \"requestingToSpeak\": \"Rekwestiŋ tü Spëk\",\n      \"listeners\": \"Lisenrz\",\n      \"allowAll\": \"Alaw əl\",\n      \"allowAllConfirm\": \"Ər yü s̈r? Þis wil alaw əl {{count}} rekwestiŋ yüzrz tü spëk\"\n    },\n    \"searchUser\": { \"search\": \"src̈...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sawndz\",\n      \"title\": \"Sawnd Setiŋz\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Edit Pröfïl\",\n      \"followsYou\": \"Folöz yü\",\n      \"followers\": \"folörz\",\n      \"following\": \"folöiŋ\",\n      \"followHim\": \"Folö\",\n      \"unfollow\": \"Unfolö\",\n      \"followingHim\": \"Folöiŋ\",\n      \"copyProfileUrl\": \"Kopë Pröfïl URL\",\n      \"urlCopied\": \"URL kopëd tü klipbörd\",\n      \"about\": \"Abawt\",\n      \"bot\": \"Bot\",\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Vöës Setiŋz\",\n      \"mic\": \"Mïk:\",\n      \"permissionError\": \"Nö mïks fawnd, yü ïþr hav nun plugd in ör havn't givn þis websït prmis̈on.\",\n      \"refresh\": \"Rëfres̈ Mïk List\",\n      \"volume\": \"Volyüm:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Övrlä Setiŋz\",\n      \"input\": { \"errorMsg\": \"Plëz entr ä valid ap tïtl\", \"label\": \"Ap tïtl\" }\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Kompönent UI Intərnas̈ənəlïzäs̈un Striŋz\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Band Yüzrz\",\n      \"unban\": \"Unban\",\n      \"noBans\": \"No won haz bën band yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Lëv Krnt Röm\",\n      \"confirmLeaveRoom\": \"Ər yü s̈r yü wont tü lëv?\",\n      \"leave\": \"Lëv\",\n      \"inviteUsersToRoomBtn\": \"Invit Yüzrz tü Rüm\",\n      \"invite\": \"Invit\",\n      \"toggleMuteMicBtn\": \"Togl Myüt\",\n      \"toggleDeafMicBtn\": \"Togl Defen\",\n      \"mute\": \"Myüt\",\n      \"unmute\": \"Unmyüt\",\n      \"deafen\": \"Defen\",\n      \"undeafen\": \"Undefen\",\n      \"makeRoomPublicBtn\": \"Mäk Rüm Public!\",\n      \"settings\": \"Setiŋz\",\n      \"speaker\": \"Spëkr\",\n      \"listener\": \"Lisenr\",\n      \"chat\": \"C̈at\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Yör devis is kurentlë not supörtd. Yü kan krëät an\",\n      \"linkText\": \"is̈ü on GitHub\",\n      \"addSupport\": \"and Ï wil trï adiŋ səport för yör devis.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Pëpol\",\n      \"online\": \"ONLIN\",\n      \"noOnline\": \"Yü hav 0 frendz onlïn rït naw\",\n      \"showMore\": \"S̈ö mör\"\n    },\n    \"inviteButton\": { \"invited\": \"Invïtd!\", \"inviteToRoom\": \"Invït tü rüm\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Prmis̈on dënïd trïiŋ tü akses yör mïk (yü mä nëd tü gö intü brawsr setiŋz and rëlöd þə pëj)\",\n      \"dismiss\": \"Dismis\",\n      \"tryAgain\": \"Trï Ägän\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Set Këbïnd\",\n      \"listening\": \"Liseniŋ...\",\n      \"toggleMuteKeybind\": \"Togl myüt këbïnd\",\n      \"toggleDeafKeybind\": \"Togl defen këbïnd\",\n      \"toggleOverlayKeybind\": \"Togl övrlä këbïnd\",\n      \"togglePushToTalkKeybind\": \"Togl pus̈-tü-tak këbïnd\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Nö ədëö konsümr för sum rëson\" },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcəmiŋ rümz\",\n      \"exploreMoreRooms\": \"Eksplör mör rümz\"\n    },\n    \"addToCalendar\": { \"add\": \"Ad tü Kalendr\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket wəz kild bï þə srvr. Þis yüs̈üalë hapnz wen yü öpn þe websit in anəþr tab.\",\n      \"reconnect\": \"Rekonekt\"\n    },\n    \"search\": {\n      \"placeholder\": \"Src̈ för rümz, yüzrz ör kategörëz\",\n      \"placeholderShort\": \"Src̈\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Laŋgwaj\",\n      \"reportABug\": \"Rëport Ä Bəg\",\n      \"useOldVersion\": \"Yüz Old Vrzon\",\n      \"logOut\": {\n        \"button\": \"Log awt\",\n        \"modalSubtitle\": \"Ər yü s̈r yü want tü logawt?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debəg Ədëö\",\n        \"stopDebugger\": \"Stop Debəgr\"\n      },\n      \"downloadApp\": \"Dawnlöd App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Fil þe folowiŋ fëldz tü start ä nü rüm\",\n        \"public\": \"Public\",\n        \"private\": \"Prïvit\",\n        \"roomName\": \"Rüm näm\",\n        \"roomDescription\": \"Rüm descrips̈on\",\n        \"descriptionError\": \"Maks lenð 500\",\n        \"nameError\": \"Must bë bëtwën 2 tü 60 karictrz lon\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nü Rüm Krëäted\",\n        \"roomInviteFrom\": \"Rüm Invit frəm\",\n        \"justStarted\": \"Þä just stərted\",\n        \"likeToJoin\": \", wəd yü lïk tü jöën?\",\n        \"inviteReceived\": \"yü'v bën invitd tü\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Yüzrnäm täkn\",\n        \"avatarUrlError\": \"Invalid imag\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar URL\",\n        \"bannerUrlLabel\": \"Twitter banr URL\",\n        \"displayNameError\": \"Lenð 2 tü 50 karaktrz\",\n        \"displayNameLabel\": \"Displä Näm\",\n        \"usernameError\": \"Lenð 4 tü 15 karaktrz and önlë alfanümerik/undrscör\",\n        \"usernameLabel\": \"Yüzrnäm\",\n        \"bioError\": \"Maks lenð uv 160 characters\",\n        \"bioLabel\": \"Bïö\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ər yü s̈r yü wont tü blok ðis yüzr frəm jöëniŋ anë rüm yü evr krëät?\",\n        \"blockUser\": \"Blok yüzr\",\n        \"makeMod\": \"Promot tü Mod\",\n        \"unmod\": \"Demot frəm Mod\",\n        \"addAsSpeaker\": \"Ad az Spëkr\",\n        \"moveToListener\": \"Müv tü Lisenr\",\n        \"unBanFromChat\": \"Unban frəm C̈at\",\n        \"banFromChat\": \"Ban frəm C̈at\",\n        \"banFromRoom\": \"Ban frəm Rüm\",\n        \"goBackToListener\": \"Gö Bak tü Lisenr\",\n        \"deleteMessage\": \"Dlët þis Mesəj\",\n        \"makeRoomCreator\": \"Promot tü Admin\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"rekwïr prmis̈on tü spëk\",\n        \"makePublic\": \"mäk rüm public\",\n        \"makePrivate\": \"mäk rüm prïvit\",\n        \"renamePublic\": \"Set public rüm näm\",\n        \"renamePrivate\": \"Set prïvit rüm näm\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modyülz UI Intərnas̈ənəlïzäs̈un Striŋz\",\n    \"feed\": { \"yourFeed\": \"Yör fëd\" },\n    \"scheduledRooms\": {\n      \"title\": \"Skejüld Rümz\",\n      \"noneFound\": \"nun fawnd\",\n      \"allRooms\": \"əl scheduled rümz\",\n      \"myRooms\": \"my skejüld rümz\",\n      \"scheduleRoomHeader\": \"skejül Rüm\",\n      \"startRoom\": \"stərt rüm\",\n      \"tommorow\": \"TÜMÖRÖ\",\n      \"today\": \"TÜDÄ\",\n      \"modal\": {\n        \"needsFuture\": \"nëdz tü bë in þə fyüc̈ur\",\n        \"roomName\": \"rüm näm\",\n        \"roomDescription\": \"Deskrips̈on\",\n        \"minLength\": \"min lenð 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"Ər yü s̈r yü wont tü dlët þis skejüld rüm?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"C̈at\",\n      \"emotesSoon\": \"[ëmötz sün]\",\n      \"bannedAlert\": \"Yü hav bën band frəm c̈at\",\n      \"waitAlert\": \"Yü hav tü wät ä secund bëfor sendiŋ anəþr mesəj\",\n      \"search\": \"Src̈\",\n      \"searchResults\": \"Src̈ Rezultz\",\n      \"recent\": \"Frekwentlë Yüzd\",\n      \"sendMessage\": \"Send ä Mesəj\",\n      \"whisper\": \"Wispr\",\n      \"welcomeMessage\": \"Welcom tü c̈at!\",\n      \"roomDescription\": \"Rüm deskrips̈on\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fyüliŋ roket\",\n      \"takingOff\": \"Takiŋ of\",\n      \"inSpace\": \"In späs\",\n      \"approachingMoon\": \"Apröc̈iŋ mün\",\n      \"lunarDoge\": \"Lünr Döj\",\n      \"approachingSun\": \"Apröc̈iŋ sun\",\n      \"solarDoge\": \"Solr Döj\",\n      \"approachingGalaxy\": \"Apröc̈iŋ galaksë\",\n      \"galacticDoge\": \"Galaktik Döj\",\n      \"spottedLife\": \"Planet wið lif spotd\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-CODE/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"loadMore();\",\n    \"loading\": \"...loading\",\n    \"noUsersFound\": \"ERR: No users found!\",\n    \"ok\": \"ok\",\n    \"yes\": \"yes\",\n    \"no\": \"no\",\n    \"cancel\": \"cancel\",\n    \"save\": \"save()\",\n    \"edit\": \"edit()\",\n    \"delete\": \"delete()\",\n    \"joinRoom\": \"joinRoom()\",\n    \"copyLink\": \"copy()\",\n    \"copied\": \"copied\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"WARNING: please note running DogeHouse without accessibility permissions may cause unwanted console errors\",\n    \"copy\": \"copy\",\n    \"error\": \"error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"Doge.House()\",\n    \"dashboard\": \"./dashboard\",\n    \"connectionTaken\": \"ERR: connection taken\",\n    \"mutedTitle\": \"mute() | Doge.House()\",\n    \"deafenedTitle\": \"deafen() | Doge.House()\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"origin story;\",\n    \"link_2\": \"discord;\",\n    \"link_3\": \"make a github issue;\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"botEdit\": {\n      \"yourBots\": \"your clients\",\n      \"bots\": \"clients\",\n      \"title\": \"client.information\",\n      \"apiKey\": \"process.env.APIKEY\",\n      \"regenerate\": \"regen();\",\n      \"reveal\": \"hack into .env\"\n    },\n    \"admin\": {\n      \"ban\": \"ban();\",\n      \"userStaffandContrib\": \"user staff & contributions\",\n      \"staff\": \"staff: \",\n      \"contributions\": \"contributions\",\n      \"username\": \"username\",\n      \"usrStaff\": \"user staff\",\n      \"usrContributions\": \"programmer contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"download\": {\n      \"prompt\": \"download the Doge.House() desktop app\",\n      \"download_for\": \"download for %platform% (%ext%)\",\n      \"failed\": \"ERR: unable to detect platform, please try again later or visit gitHub releases\",\n      \"visit_gh\": \"visit gitHub releases\"\n    },\n    \"followingOnlineList\": {\n      \"title\": \"programmers\",\n      \"listHeader\": \"list of programmers that are not in a private room and you follow.\",\n      \"currentRoom\": \"currently in:\",\n      \"startPrivateRoom\": \"start a private room with them\"\n    },\n    \"followList\": {\n      \"title\": \"programmers\",\n      \"followHim\": \"follow();\",\n      \"followingHim\": \"programmer.following\",\n      \"followingNone\": \"not following anyone\",\n      \"noFollowers\": \"no followers | null\"\n    },\n    \"home\": {\n      \"createRoom\": \"new file\",\n      \"editRoom\": \"edit file\",\n      \"refresh\": \"refresh\",\n      \"desktopAlert\": \"download the Doge.House() desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"room gone; return;\",\n      \"shareRoomLink\": \"link.share()\",\n      \"inviteFollowers\": \"you can invite your followers that are online:\",\n      \"whenFollowersOnline\": \"when your followers are online, they will show up here.\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"dark theme\",\n      \"featureText_2\": \"sign-ups.open();\",\n      \"featureText_3\": \"cross-platform support\",\n      \"featureText_4\": \"open source\",\n      \"featureText_5\": \"sonar chat\",\n      \"featureText_6\": \"powered by doge\",\n      \"loginGithub\": \"login with gitHub\",\n      \"loginTwitter\": \"login with twitter\",\n      \"loginDiscord\": \"login with discord\",\n      \"createTestUser\": \"create test programmer\"\n    },\n    \"myProfile\": {\n      \"logout\": \"user.logout();\",\n      \"probablyLoading\": \"probably loading...\",\n      \"voiceSettings\": \"voice-config.json\",\n      \"overlaySettings\": \"overlay-config.json\",\n      \"soundSettings\": \"sound-config.json\",\n      \"deleteAccount\": \"account.delete();\",\n      \"couldNotFindUser\": \"ERR: could not find 'programmer' of null\",\n      \"privacySettings\": \"privacy-config.json\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"ERR: this page got lost in conversation.\",\n      \"goHomeMessage\": \"not to worry. you can. npm will handle it for you\",\n      \"goHomeLinkText\": \"home.js\"\n    },\n    \"privacySettings\": {\n      \"title\": \"privacy-config.json\",\n      \"header\": \"privacy-config.json\",\n      \"whispers\": {\n        \"label\": \"whispers\",\n        \"on\": \".on()\",\n        \"off\": \".off()\"\n      }\n    },\n    \"room\": {\n      \"speakers\": \"professors\",\n      \"requestingToSpeak\": \"requesting to teach\",\n      \"listeners\": \"students\",\n      \"allowAll\": \"allowAll: true\",\n      \"allowAllConfirm\": \"WARNING: are you sure? This will allow all {{count}} requesting programmers to speak\"\n    },\n    \"searchUser\": {\n      \"search\": \"ctrl+f...\"\n    },\n    \"soundEffectSettings\": {\n      \"title\": \"sound-config.json\",\n      \"header\": \"sounds\",\n      \"playSound\": \"play sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"edit profile.json\",\n      \"followsYou\": \"follows you: true\",\n      \"followers\": \"followers\",\n      \"following\": \"following\",\n      \"followHim\": \"follow();\",\n      \"unfollow\": \"unfollow()\",\n      \"followingHim\": \"following: true\",\n      \"block\": \"block({reason: 'bad'})\",\n      \"unblock\": \"unblock()\",\n      \"sendDM\": \"send dm\",\n      \"copyProfileUrl\": \"profileURL.copy();\",\n      \"urlCopied\": \"copied\",\n      \"about\": \"about\",\n      \"aboutSuffix\": \"\",\n      \"bot\": \"bot: true\",\n      \"errors\": {\n        \"blocked\": \"ERR: this programmer has blocked you.\",\n\n        \"default\": \"ERR: couldn't load this programmer.\"\n      },\n      \"profileTabs\": {\n        \"about\": \"_about\",\n        \"rooms\": \"_rooms\",\n        \"scheduled\": \"_scheduled\",\n        \"recorded\": \"_Recorded\",\n        \"clips\": \"_clips\",\n        \"admin\": \"_admin\"\n      }\n    },\n    \"voiceSettings\": {\n      \"title\": \"voice-config.json\",\n      \"header\": \"voice-config.json\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"ERR: no mics found, you either have none plugged in or haven't given this website permission.\",\n      \"refresh\": \"mic-list.refresh()\",\n      \"volume\": \"volume:\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"overlay-config.json\",\n      \"input\": {\n        \"errorMsg\": \"ERR: please enter a valid app title\",\n        \"label\": \"app.title\"\n      }\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"room.banned\",\n      \"unban\": \"unban();\",\n      \"noBans\": \"node(6969): no one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"room.leave()\",\n      \"confirmLeaveRoom\": \"WARNING: are you sure you want to leave?\",\n      \"leave\": \"room.leave()\",\n      \"inviteUsersToRoomBtn\": \"room.inviteURL\",\n      \"invite\": \"invite()\",\n      \"toggleMuteMicBtn\": \"toggleMute()\",\n      \"toggleDeafMicBtn\": \"toggleDeafen()\",\n      \"mute\": \"mute()\",\n      \"unmute\": \"unmute()\",\n      \"deafen\": \"deafen()\",\n      \"undeafen\": \"undeafen()\",\n      \"makeRoomPublicBtn\": \"room.public: true\",\n      \"settings\": \"config.json\",\n      \"speaker\": \"speaker\",\n      \"listener\": \"listener\",\n      \"chat\": \"console\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"ERR: your device is currently not supported. you can create an\",\n      \"linkText\": \"issue on gitHub\",\n      \"addSupport\": \"and i will try adding support for your device.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"programmers\",\n      \"online\": \"CODING\",\n      \"noOnline\": \"friends-online: 0;\",\n      \"showMore\": \"fetchMore()\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"invited!\",\n      \"inviteToRoom\": \"invite to room\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"console logs\",\n      \"showMore\": \"fetchMore()\",\n      \"noMessages\": \"no new console logs\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"ERR: permission denied trying to access your mic (you may need to go into browser settings and reload the page)\",\n      \"dismiss\": \"dismiss\",\n      \"tryAgain\": \"try again\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"setKeybind()\",\n      \"listening\": \"listening...\",\n      \"toggleMuteKeybind\": \"<toggle mute keybind>\",\n      \"toggleDeafKeybind\": \"<toggle deafen keybind>\",\n      \"toggleOverlayKeybind\": \"<toggle overlay keybind>\",\n      \"togglePushToTalkKeybind\": \"<toggle push-to-talk keybind>\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"ERR: no audio consumer for some reason\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"upcoming files\",\n      \"exploreMoreRooms\": \"explore more files\"\n    },\n    \"addToCalendar\": {\n      \"add\": \"calendar.add()\"\n    },\n    \"wsKilled\": {\n      \"description\": \"app crashed: websocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"reconnect()\"\n    },\n    \"search\": {\n      \"placeholder\": \"search for files, programmers or categories\",\n      \"placeholderShort\": \"find()\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"profile\",\n      \"language\": \"language\",\n      \"reportABug\": \"report a bug\",\n      \"useOldVersion\": \"use old version\",\n      \"logOut\": {\n        \"button\": \"logout()\",\n        \"modalSubtitle\": \"WARNING: are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"audio.debug()\",\n        \"stopDebugger\": \"debugger.stop()\"\n      },\n      \"downloadApp\": \"download app\",\n      \"developer\": \"developer-config.json\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"Doge.House() staff\",\n      \"dhContributor\": \"Doge.House() contributor\"\n    },\n    \"modals\": {\n      \"createBotModal\": {\n        \"usernameTaken\": \"username is taken\",\n        \"subtitle\": \"please fill the details below to create your client\",\n        \"title\": \"create client\"\n      },\n      \"createRoomModal\": {\n        \"subtitle\": \"> fill the following fields to start a new room\",\n        \"public\": \"...public\",\n        \"private\": \"...private\",\n        \"roomName\": \"file name\",\n        \"roomDescription\": \"file description\",\n        \"descriptionError\": \"max length 500\",\n        \"nameError\": \"ERR: must be between 2 to 60 characters long\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"new file Created\",\n        \"roomInviteFrom\": \"file invite from\",\n        \"justStarted\": \"they just started\",\n        \"likeToJoin\": \", would you like to join?\",\n        \"inviteReceived\": \"you've been invited to\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"username taken\",\n        \"avatarUrlError\": \"invalid image\",\n        \"avatarUrlLabel\": \"github/twitter/discord avatar URL\",\n        \"bannerUrlLabel\": \"twitter banner URL\",\n        \"displayNameError\": \"length 2 to 50 characters\",\n        \"displayNameLabel\": \"display name\",\n        \"usernameError\": \"length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"username\",\n        \"bioError\": \"max length of 160 characters\",\n        \"bioLabel\": \"bio\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"WARNING: are you sure you want to block this programmer from joining any file you ever create?\",\n        \"blockUser\": \"block programmer\",\n        \"makeMod\": \"promote to mod\",\n        \"unmod\": \"demote from mod\",\n        \"addAsSpeaker\": \"speaker.add()\",\n        \"moveToListener\": \"listener.add()\",\n        \"unBanFromChat\": \"unban from console\",\n        \"banFromChat\": \"ban from console\",\n        \"banFromRoom\": \"ban from file\",\n        \"banIPFromRoom\": \"ban IP from file\",\n        \"goBackToListener\": \"go back to listener\",\n        \"deleteMessage\": \"delete this log\",\n        \"makeRoomCreator\": \"setLevel(ADMIN)\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"require permission to speak\",\n        \"chatCooldown\": \"chat cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"console\",\n          \"enabled\": \"enabled\",\n          \"disabled\": \"disabled\",\n          \"followerOnly\": \"follower only\"\n        },\n        \"makePublic\": \"make file public\",\n        \"makePrivate\": \"make file private\",\n        \"renamePublic\": \"set public file name\",\n        \"renamePrivate\": \"set private file name\"\n      }\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": {\n      \"yourFeed\": \"your feed\"\n    },\n    \"scheduledRooms\": {\n      \"title\": \"scheduled files\",\n      \"noneFound\": \"none found\",\n      \"allRooms\": \"all scheduled files\",\n      \"myRooms\": \"my scheduled files\",\n      \"scheduleRoomHeader\": \"schedule file\",\n      \"startRoom\": \"start file\",\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"modal\": {\n        \"needsFuture\": \"needs to be in the future\",\n        \"roomName\": \"file name\",\n        \"roomDescription\": \"description\",\n        \"minLength\": \"min length 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"are you sure you want to delete this file?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"console\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"you have been banned from console\",\n      \"waitAlert\": \"you have to wait a second before sending another command. greedy programmer\",\n      \"search\": \"search(query)\",\n      \"searchResults\": \"search results\",\n      \"recent\": \"frequently used\",\n      \"sendMessage\": \"send a command\",\n      \"whisper\": \"whisper\",\n      \"welcomeMessage\": \"welcome to console!\",\n      \"roomDescription\": \"file description\",\n      \"disabled\": \"roomChatDisabled: true\",\n      \"messageDeletion\": {\n        \"message\": \"command\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"recylced\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"fuelingRocket: true\",\n      \"takingOff\": \"takingOff: true\",\n      \"inSpace\": \"inSpace: true\",\n      \"approachingMoon\": \"approaching united states\",\n      \"lunarDoge\": \"lunar doge\",\n      \"approachingSun\": \"approaching massachusetts\",\n      \"solarDoge\": \"solar doge\",\n      \"approachingGalaxy\": \"approaching MIT\",\n      \"galacticDoge\": \"galactic doge\",\n      \"spottedLife\": \"planet with programmers spotted\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-LOLCAT/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"mak mor apper\",\n    \"loading\": \"lodin........\",\n    \"noUsersFound\": \"no kittehs here :(\",\n    \"ok\": \"k\",\n    \"yes\": \"yeh\",\n    \"no\": \"na\",\n    \"cancel\": \"nvermind\",\n    \"save\": \"sav\",\n    \"edit\": \"mak difernt\",\n    \"delete\": \"thow in kitteh litter\",\n    \"joinRoom\": \"go in kitteh howse\",\n    \"copyLink\": \"get da liNk\",\n    \"copied\": \"got de liNk\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"KittehHowse\",\n    \"dashboard\": \"dashbored\",\n    \"connectionTaken\": \"connecty thing is used\",\n    \"mutedTitle\": \"no spek | KittehHowse\",\n    \"deafenedTitle\": \"deaf | KittehHowse\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"wy dis exsit\",\n    \"link_2\": \"dizcord\",\n    \"link_3\": \"eew i fownd a buggy\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"banish\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"download\": {\n      \"prompt\": \"get da desktp ap\",\n      \"download_for\": \"get fur %platform% (%ext%)\",\n      \"failed\": \"idk wut platform u usin\",\n      \"visit_gh\": \"visit github releases\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"kittehs who are not lookn at their privates an u stalk dem\",\n      \"currentRoom\": \"rn ur in:\",\n      \"startPrivateRoom\": \"go in private howse wit dem\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"stalk\",\n      \"followingHim\": \"stalking\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"mak new howse\",\n      \"editRoom\": \"mak howse difernt\",\n      \"refresh\": \"refresh\",\n      \"desktopAlert\": \"get desktp ap NOW or meh skratch u\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"no more howse, go away\",\n      \"shareRoomLink\": \"shar howse lnk\",\n      \"inviteFollowers\": \"invte ur stalkers who iz online rn:\",\n      \"whenFollowersOnline\": \"when ur stalkers iz online, dey apper here.\"\n    },\n    \"login\": {\n      \"headerText\": \"we get ur voice and yeet it to da moon 🚀\",\n      \"featureText_1\": \"no lite theme cuz it cringe\",\n      \"featureText_2\": \"any kitteh can join uz\",\n      \"featureText_3\": \"use on many many many diff device\",\n      \"featureText_4\": \"u can see da code and stare at it\",\n      \"featureText_5\": \"u can type wordz too!!!!!!!\",\n      \"featureText_6\": \"Powered by Kittehs\",\n      \"loginGithub\": \"login with gitub\",\n      \"loginTwitter\": \"login with twitr\",\n      \"loginDiscord\": \"login with dizcord\",\n      \"createTestUser\": \"mak fake testn user\"\n    },\n    \"myProfile\": {\n      \"logout\": \"go away\",\n      \"probablyLoading\": \"it mite be loadng.........\",\n      \"voiceSettings\": \"speekn settns\",\n      \"overlaySettings\": \"ovrlai settns\",\n      \"soundSettings\": \"sond settns\",\n      \"deleteAccount\": \"get acont an yeet it into kitteh litter\",\n      \"couldNotFindUser\": \"Sozz, but dat user no exist\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"dis page tok a wrong turn and got lost\",\n      \"goHomeMessage\": \"ay u, \",\n      \"goHomeLinkText\": \"go ta ur howse\"\n    },\n    \"room\": {\n      \"speakers\": \"spekars\",\n      \"requestingToSpeak\": \"meh wants ta spek\",\n      \"listeners\": \"listenars\",\n      \"allowAll\": \"let all spek\",\n      \"allowAllConfirm\": \"u crazy? dis will let {{count}} kittehs talk\"\n    },\n    \"searchUser\": { \"search\": \"search...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sounds\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"mak profile difernt\",\n      \"followsYou\": \"stalks u\",\n      \"followers\": \"kittehs stalking u\",\n      \"following\": \"kittehs u stalk\",\n      \"followHim\": \"Follow\",\n      \"unfollow\": \"Unfollow\",\n      \"followingHim\": \"Following\",\n      \"copyProfileUrl\": \"Copy Profile URL\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Voice Settings\",\n      \"mic\": \"Mic:\",\n      \"permissionError\": \"No mics found, you either have none plugged in or haven't given this website permission.\",\n      \"refresh\": \"Refresh Mic List\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter a valid app title\",\n        \"label\": \"App title\"\n      }\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Banned Users\",\n      \"unban\": \"Unban\",\n      \"noBans\": \"No one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Leave Current Room\",\n      \"confirmLeaveRoom\": \"Are you sure you want to leave?\",\n      \"leave\": \"Leave\",\n      \"inviteUsersToRoomBtn\": \"Invite Users to Room\",\n      \"invite\": \"Invite\",\n      \"toggleMuteMicBtn\": \"Toggle Mute\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\",\n      \"makeRoomPublicBtn\": \"Make Room Public!\",\n      \"settings\": \"Settings\",\n      \"speaker\": \"Speaker\",\n      \"listener\": \"Listener\",\n      \"chat\": \"Chat\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Your device is currently not supported. You can create an\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"and I will try adding support for your device.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Kittehs\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"u has no fwens\",\n      \"showMore\": \"Show more\"\n    },\n    \"inviteButton\": { \"invited\": \"Invited!\", \"inviteToRoom\": \"Invite to room\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permission denied trying to access your mic (you may need to go into browser settings and reload the page)\",\n      \"dismiss\": \"Dismiss\",\n      \"tryAgain\": \"Try Again\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Set Keybind\",\n      \"listening\": \"Listening...\",\n      \"toggleMuteKeybind\": \"Toggle mute keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\",\n      \"toggleOverlayKeybind\": \"Toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"Toggle push-to-talk keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"No audio consumer for some reason\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore more rooms\"\n    },\n    \"addToCalendar\": { \"add\": \"Add to Calendar\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"Reconnect\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Fill the following fields to start a new room\",\n        \"public\": \"Public\",\n        \"private\": \"Private\",\n        \"roomName\": \"Room name\",\n        \"roomDescription\": \"Room description\",\n        \"descriptionError\": \"Max length 500\",\n        \"nameError\": \"Must be between 2 to 60 characters long\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"New Room Created\",\n        \"roomInviteFrom\": \"Room Invite from\",\n        \"justStarted\": \"They just started\",\n        \"likeToJoin\": \", would you like to join?\",\n        \"inviteReceived\": \"you've been invited to\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar URL\",\n        \"bannerUrlLabel\": \"Twitter banner URL\",\n        \"displayNameError\": \"Length 2 to 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"Length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"Max length of 160 characters\",\n        \"bioLabel\": \"Bio\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Are you sure you want to block this user from joining any room you ever create?\",\n        \"blockUser\": \"Block user\",\n        \"makeMod\": \"Promote to Mod\",\n        \"unmod\": \"Demote from Mod\",\n        \"addAsSpeaker\": \"Add as Speaker\",\n        \"moveToListener\": \"Move to Listener\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banFromChat\": \"Ban from Chat\",\n        \"banFromRoom\": \"Ban from Room\",\n        \"goBackToListener\": \"Go Back to Listener\",\n        \"deleteMessage\": \"Delete this Message\",\n        \"makeRoomCreator\": \"Promote to Admin\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"require permission to speak\",\n        \"makePublic\": \"make room public\",\n        \"makePrivate\": \"make room private\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Your feed\" },\n    \"scheduledRooms\": {\n      \"title\": \"Scheduled Rooms\",\n      \"noneFound\": \"none found\",\n      \"allRooms\": \"all scheduled rooms\",\n      \"myRooms\": \"my scheduled rooms\",\n      \"scheduleRoomHeader\": \"Schedule Room\",\n      \"startRoom\": \"start room\",\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"modal\": {\n        \"needsFuture\": \"needs to be in the future\",\n        \"roomName\": \"room name\",\n        \"roomDescription\": \"Description\",\n        \"minLength\": \"min length 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"You have been banned from chat\",\n      \"waitAlert\": \"You have to wait a second before sending another message\",\n      \"search\": \"Search\",\n      \"searchResults\": \"Search Results\",\n      \"recent\": \"Frequently Used\",\n      \"sendMessage\": \"Send a Message\",\n      \"whisper\": \"Whisper\",\n      \"welcomeMessage\": \"Welcome to chat!\",\n      \"roomDescription\": \"Room description\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-OWO/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"0w0 Woad Mowe UwU\",\n    \"loading\": \"<3 Woading... ;-;\",\n    \"noUsersFound\": \"<3 Nu usews found T_T\",\n    \"ok\": \"okie UwU xD\",\n    \"yes\": \"yes xD\",\n    \"no\": \"no xD\",\n    \"cancel\": \"Cancew <{^v^}>\",\n    \"save\": \"0w0 Save (T w T)\",\n    \"edit\": \"UwU Edit\",\n    \"delete\": \"UnU Dewete\",\n    \"joinRoom\": \"Join Woom ^_^\",\n    \"copyLink\": \"Copy Wink :3\",\n    \"copied\": \"Copied ^-^\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"<3 Pwease note ruwwing DwogeHouse without accessabiwity pewmissiowns may cause ewwows （･_･)\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DwogeHouse :3\",\n    \"mutedTitle\": \"Muted | DwogeHouse xD\",\n    \"deafenedTitle\": \"Deafened | DwogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \" Owigin Stowy ʕʘ‿ʘʔ\",\n    \"link_2\": \"Discord o_o\",\n    \"link_3\": \"Wepowt a Bug UwU (✿ ♡‿♡)\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban >:C\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason UwU\",\n      \"usernamePlaceholder\": \"username to perform actions on >:)\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"<3 Wist of usews that awe nwot in a pwivate woom and uu fowwow. :3\",\n      \"currentRoom\": \"Cuwwentwy in:\",\n      \"startPrivateRoom\": \"<3 Stawt a pwivate woom with them xD\",\n      \"title\": \"Fuwwies (People)\"\n    },\n    \"followList\": {\n      \"followHim\": \"Follow >_<\",\n      \"followingHim\": \"Following :O\",\n      \"title\": \"Fuwwies (People)\",\n      \"followingNone\": \"Not following anyone >⌓<\",\n      \"noFollowers\": \"No followers >⌓<\"\n    },\n    \"home\": {\n      \"createRoom\": \"Cweate Woom (◕ω◕)\",\n      \"refresh\": \" Wefwesh (◠‿◠✿)\",\n      \"editRoom\": \"Edit Woom\",\n      \"desktopAlert\": \"Downwoad the DwogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Woom gone, go back <{^v^}>\",\n      \"shareRoomLink\": \"Shawe Wink to Woom (❁´◡`❁)\",\n      \"inviteFollowers\": \"OwO You can invite yowr fowwowers that awe onwine:\",\n      \"whenFollowersOnline\": \"UwU When yowr fowwowews awe onwine, they wiww show up hewe. ÙωÙ\"\n    },\n    \"login\": {\n      \"headerText\": \"OwO Taking voice convewsations to da moon 🚀 (• o •)\",\n      \"featureText_1\": \"Darcc Theme (°ω° )\",\n      \"featureText_2\": \"Open Sign-Ups, fwendo\",\n      \"featureText_3\": \"OWO Cwoss-Pwatfowm Suppowt（＾ｖ＾）\",\n      \"featureText_4\": \"0w0 Open Souwce ÙωÙ\",\n      \"featureText_5\": \"Text Chat <{^v^}>\",\n      \"featureText_6\": \"Powewed by Dwoge (๑ᴖ◡ᴖ)\",\n      \"loginGithub\": \"Log in with GitHub\",\n      \"loginTwitter\": \"Log in with Twittew\",\n      \"createTestUser\": \"Cweate Test Usew UwU\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Logout\",\n      \"probablyLoading\": \"Pwobabwy woading... (๑ᴖ◡ᴖ)\",\n      \"voiceSettings\": \"<3 Go to Voice Settings ;-;\",\n      \"soundSettings\": \"<3 Go to Sound Settings ;-;\",\n      \"deleteAccount\": \"Dewete Account T_T\",\n      \"overlaySettings\": \"Gow to Owerlay Settings (๑^_^๑)\",\n      \"couldNotFindUser\": \"Sowwy, we could not find that user :(\",\n      \"privacySettings\": \"Privacy settings ʕ·ᴥʔ\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"OWO Whoops! This page got wost in convewsation. (●´ω｀●)\",\n      \"goHomeMessage\": \"Not to wowwy. You can\",\n      \"goHomeLinkText\": \"go home (・ω・｀)..\"\n    },\n    \"room\": {\n      \"speakers\": \"Speakews :O\",\n      \"requestingToSpeak\": \"Wequesting to speak (╯﹏╰）\",\n      \"listeners\": \"Wistenews (´・ω・｀)\",\n      \"allowAll\": \"Allow all (~_~)\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak (〃ω 〃)\"\n    },\n    \"searchUser\": { \"search\": \"Seawch... >~<\" },\n    \"soundEffectSettings\": {\n      \"header\": \"OwO Sounds :D\",\n      \"title\": \"Sound Settings :O\",\n      \"playSound\": \"Play Sound :O\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Edit Pwofiwe (⁎˃ᆺ˂)\",\n      \"followsYou\": \"Fowwows uu (๑•́ ₃ •̀๑)\",\n      \"followers\": \"Fowwowews >_>\",\n      \"following\": \"Fowwowing ( ͡° ᴥ ͡°)\",\n      \"followHim\": \"Fowwow uwu\",\n      \"followingHim\": \"Fowwowing <3\",\n      \"copyProfileUrl\": \"Copy Pwofile URL\",\n      \"urlCopied\": \"URL Cwopied to Cwipboard (❤´艸｀❤)\",\n      \"unfollow\": \"Unfowwow\",\n      \"about\": \"Awout\",\n      \"bot\": \"Bot └[・ಎ・]┘\",\n      \"profileTabs\": {\n        \"about\": \"Awout\",\n        \"rooms\": \"Wooms\",\n        \"scheduled\": \"Scheduwed\",\n        \"recorded\": \"Recorwed\",\n        \"clips\": \"Cwips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Bwock\",\n      \"unblock\": \"Unbwock\",\n      \"sendDM\": \"Send DM <3\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"OwO Voice Settings (✿ ♡‿♡)\",\n      \"mic\": \"Mic:\",\n      \"permissionError\": \"Nu mics found, uu eithew has nwne pwugged in ow hasn't given this website pewmission. ;_;\",\n      \"refresh\": \"Wefwesh mic wist (｀へ´)\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Invawid awpp title\",\n        \"label\": \"Enter Awpp Title\"\n      },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting downwoad...\",\n      \"failed\": \"Could not auto-downwoad, pwease try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Cwick on the button below to start downwoad\",\n      \"download_now\": \"Downwoad Now\",\n      \"download_for\": \"Downwoad for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Priwacy Settings\",\n      \"header\": \"Priwacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On :)\", \"off\": \"Off :(\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bwanned Usews, fwendo\",\n      \"unban\": \"Unbwan\",\n      \"noBans\": \"No one has been bwanned yet (>_<)\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Weave cuwwent woom T_T\",\n      \"confirmLeaveRoom\": \"Awe uu suwe uu want to weave? (๑•́ ₃ •̀๑)\",\n      \"leave\": \"Weave\",\n      \"inviteUsersToRoomBtn\": \"Inwite Usews to Woom :D\",\n      \"invite\": \"Inwite\",\n      \"toggleMuteMicBtn\": \"Toggwe Mute Micwophone (　'◟ ')\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"makeRoomPublicBtn\": \"OwO Make Woom Pubwic! (❁´◡`❁)\",\n      \"settings\": \"Settings ^_^\",\n      \"speaker\": \"Speakew :D\",\n      \"listener\": \"OwO Wistenew\",\n      \"chat\": \"Chat :O\",\n      \"toggleDeafMicBtn\": \"Twoggle Dweafen >_<\",\n      \"deafen\": \"Dweafen\",\n      \"undeafen\": \"Undweafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"HIIII XDD! Youw dewice is cuwwentwy nut suppowted. You can cweate an\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"and I wiww twy adding suppowt fow uuw device. :D\"\n    },\n    \"inviteButton\": {\n      \"inwited\": \"Inwited!\",\n      \"inwiteToRoom\": \"Inwite to Woom\",\n      \"invited\": \"Invited!\",\n      \"inviteToRoom\": \"Invite to room\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Huohhhh. Pewmission denied twying to access uuw mic (uu may need to go into bwowsew settings and wewoad da page) (❁´◡`❁)\",\n      \"dismiss\": \"Dismiss\",\n      \"tryAgain\": \"Twy Again\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Set Keybind\",\n      \"listening\": \"OwO Liswening\",\n      \"toggleMuteKeybind\": \"0w0 Toggwe Mute keybind (❁´◡`❁)\",\n      \"togglePushToTalkKeybind\": \"Toggwe Pwush-to-Tawk keybind :D\",\n      \"toggleOverlayKeybind\": \"Toggwe Owerlay keybind :3\",\n      \"toggleDeafKeybind\": \"Toggwe dweafen keybind :3\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Nu audio consumew fow some weason D:\"\n    },\n    \"addToCalendar\": { \"add\": \"Add to Cawendaw ^_^\" },\n    \"wsKilled\": {\n      \"description\": \" WebSocket was kiwwed by da sewvew. This usuawwy happens when uu open da website in anuthew tab. ;3\",\n      \"reconnect\": \"Weconnect (；ω；)\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Pubwic\",\n        \"private\": \"UwU Pwivate ^_^\",\n        \"roomName\": \"Woom name ʕʘ‿ʘʔ\",\n        \"roomDescription\": \"OwO Woom descwiption :D\",\n        \"descriptionError\": \"Max wength 500 (●´ω｀●)\",\n        \"nameError\": \"Must be between 2 to 60 chawactews wong :3\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"0w0 New Woom Cweated (●´ω｀●)\",\n        \"roomInviteFrom\": \"0w0 Woom Invite fwom :D\",\n        \"justStarted\": \"0w0 They just stawted ^_^\",\n        \"likeToJoin\": \", wouwd uu wike to join? ^-^\",\n        \"inviteReceived\": \"uu've been invited to\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Usewname taken (人◕ω◕)\",\n        \"avatarUrlError\": \"Invawid image (⁎˃ᆺ˂)\",\n        \"avatarUrlLabel\": \"GitHub/Twittew avataw uww UwU\",\n        \"displayNameError\": \"wength 2 to 50 chawactews ㅇㅅㅇ\",\n        \"displayNameLabel\": \"<3 Dispway Name\",\n        \"usernameError\": \"OwO wength 4 to 15 chawactews and onwy awphanumewic/undewscowe (இωஇ )\",\n        \"usernameLabel\": \"UwU Usewname UwU\",\n        \"bioError\": \"Max wength of 160 chawactews :D\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twittew banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"OwO Awe uu suwe uu want to bwock this usew fwom joining any woom uu evew cweate? (　'◟ ')\",\n        \"blockUser\": \"Bwock Usew ʕʘ‿ʘʔ\",\n        \"makeMod\": \"Make Mod ;)\",\n        \"unmod\": \"Unmod\",\n        \"addAsSpeaker\": \"UwU Add as Speakew (人◕ω◕)\",\n        \"moveToListener\": \"Move to Wistenew ;3\",\n        \"banFromChat\": \"</3 Bwan fwom Chat (இωஇ )\",\n        \"banFromRoom\": \"Bwan fwom Woom UwU\",\n        \"goBackToListener\": \"Go Back to Wistenew (╯﹏╰）\",\n        \"deleteMessage\": \"Dewete this Message >_>\",\n        \"makeRoomCreator\": \"Make Woom Admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Bwan IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Wequiwe pewmission to speak <{^n^}>\",\n        \"makePublic\": \"UwU Make Woom Pubwic (⁎˃ᆺ˂)\",\n        \"makePrivate\": \"Make Woom Pwivate xD\",\n        \"renamePublic\": \"Set Public Woom Name\",\n        \"renamePrivate\": \"Set Pwivate Woom Name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cwoldown (miwwiseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabwed\",\n          \"disabled\": \"Disabwed\",\n          \"followerOnly\": \"Fowwower Onwy\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Fuwwies (People)\",\n      \"online\": \"ONWINE\",\n      \"noOnline\": \"You hv 0 fwends onwine wight now >_<\",\n      \"showMore\": \"Show mowe\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming Wooms :D\",\n      \"exploreMoreRooms\": \"Expwore Mowe Wooms :O\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for wooms, usews or categowies :O\",\n      \"placeholderShort\": \"Seawch\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Pwofile\",\n      \"language\": \"Language UwU\",\n      \"reportABug\": \"Repowt A Bug\",\n      \"useOldVersion\": \"Use Old Vewsion\",\n      \"logOut\": {\n        \"button\": \"Log out D:\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Downwoad App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DwogeHouse Stwaff\",\n      \"dhContributor\": \"DwogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show Mowe\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Intewationalization Stwings\",\n    \"scheduledRooms\": {\n      \"title\": \"Scheduwed Wooms (• o •)\",\n      \"noneFound\": \"Noone fwound ;3\",\n      \"allRooms\": \"<3 Aww Scheduwed Wooms <3\",\n      \"myRooms\": \"My Scheduwed Wooms x3\",\n      \"scheduleRoomHeader\": \"Scheduwe Woom >_<\",\n      \"startRoom\": \"<3 Stawt Woom ;_;\",\n      \"modal\": {\n        \"needsFuture\": \"Nweeds to be in da futuwe (人◕ω◕)\",\n        \"roomName\": \"Woom Name (◠‿◠✿)\",\n        \"minLength\": \"<3 min wength 2 ^_^\",\n        \"roomDescription\": \"Descwiption\"\n      },\n      \"tommorow\": \"TOMMOWOW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Awe you suwe you want to delete this scheduled woom? [._.]\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emwotes soon ._.]\",\n      \"bannedAlert\": \"You got bwanned fwom chat (๑•́ ₃ •̀๑)\",\n      \"waitAlert\": \"You haz to wait a second befowe sending anuthew message ( ͡° ᴥ ͡°)\",\n      \"search\": \"UwU Seawch (；ω；)\",\n      \"searchResults\": \"<3 Seawch Wesuwts\",\n      \"recent\": \"Fwequentwy Used :P\",\n      \"sendMessage\": \"Send a Message xD\",\n      \"whisper\": \"Whispew <{^v^}>\",\n      \"welcomeMessage\": \"UwU Wewcome to chat! (✿ ♡‿♡)\",\n      \"roomDescription\": \"Woom Descwiption（＾ｖ＾）\",\n      \"disabled\": \"woom chat has been disabwed\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retwacted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fuewing rocket OwO\",\n      \"takingOff\": \"Tawking off :O\",\n      \"inSpace\": \"In space ʕʘ‿ʘʔ\",\n      \"approachingMoon\": \"Appwoaching moon  (❁´◡`❁)\",\n      \"lunarDoge\": \"Lunar dwoge (；ω；)\",\n      \"approachingSun\": \"Appwoaching sun >_<\",\n      \"solarDoge\": \"Solar dwoge :3\",\n      \"approachingGalaxy\": \"Appwoaching galaxy (◠‿◠)\",\n      \"galacticDoge\": \"Galactic Dwoge\",\n      \"spottedLife\": \"Pwanet with fuwwy life spotted (• o •)\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-PIGLATIN/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"oad-lay ore-may\",\n    \"loading\": \"oading-lay...\",\n    \"noUsersFound\": \"o-nay sers-uay ound-fay!\",\n    \"ok\": \"Kay-ay\",\n    \"yes\": \"Es-yay\",\n    \"no\": \"o-nay\",\n    \"cancel\": \"ancel-cay\",\n    \"save\": \"ave-say\",\n    \"edit\": \"dit-ay\",\n    \"delete\": \"elete-day\",\n    \"joinRoom\": \"oin-jay oom-ray\",\n    \"copyLink\": \"opy-cay ink-lay\",\n    \"copied\": \"opied-cay!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"lease-pay ote-nay unning-ray oge-day huse-hay ithout-way ccessibility-ay ermissions-pay ay-may ause-cay nwanted-uay rrors-eay\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"oge-Day ouse-Hay\",\n    \"dashboard\": \"ashboard-Day\",\n    \"connectionTaken\": \"onnection-cay aken-tay\",\n    \"mutedTitle\": \"uted-may | oge-day ouse-hay\",\n    \"deafenedTitle\": \"eafened-day | oge-day ouse-hay\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origin-way ory-stay\",\n    \"link_2\": \"Iscord-day\",\n    \"link_3\": \"Eport-ray a-way Ug-bay\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"an0bay\",\n      \"userStaffandContrib\": \"er-usay aff-stay & ibutions-contray\",\n      \"staff\": \"Aff-stay: \",\n      \"contributions\": \"ibutions-Contray\",\n      \"username\": \"Ername-usay\",\n      \"usrStaff\": \"er-usay aff-stay \",\n      \"usrContributions\": \"er-usay ibutions-Contray\",\n      \"reason\": \"eason-ray\",\n      \"usernamePlaceholder\": \"ername-usay o-tay erform-pay ions-actay n-oay\"\n    },\n    \"download\": {\n      \"prompt\": \"download-day he-tay doge-day ouse-hay esktop-day pp-ay\",\n      \"download_for\": \"ownload-day or-fay %platform% (%ext%)\",\n      \"failed\": \"nable-unay to detect platform, please try again later or visit GitHub Releases\",\n      \"visit_gh\": \"isit-vay it-gay ub-hay leases-ray\"\n    },\n    \"followingOnlineList\": {\n      \"title\": \"Eople-pay\",\n      \"listHeader\": \"Ist-lay f-oay ers-usay at-thay re-ay ot-nay n-iay a rivate-pay oom-ray and u-yoay low-folay.\",\n      \"currentRoom\": \"Urrently-cay N-iay\",\n      \"startPrivateRoom\": \"Art-stay a ivate-pray oom-ray ith-way em-thay\"\n    },\n    \"followList\": {\n      \"title\": \"Eople-pay\",\n      \"followHim\": \"Ollow-fay\",\n      \"followingHim\": \"Ollowing-fay\",\n      \"followingNone\": \"Ot-nay ollowing-fay one-anyay\",\n      \"noFollowers\": \"O-nay ollowers-fay\"\n    },\n    \"home\": {\n      \"createRoom\": \"Ew-nay oom-ray\",\n      \"editRoom\": \"It-eday oom-ray\",\n      \"refresh\": \"Efresh-ray\",\n      \"desktopAlert\": \"Ownload-day he-thay oge-day ouse-hay esktop-day pp-ay oday-ay!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Room-ray one-gay; o-gay ack-bay\",\n      \"shareRoomLink\": \"are-shay oom-ray ink-lay\",\n      \"inviteFollowers\": \"U-uay an-cay vite-inay ur-yay ollowers-fay hat-thay re-ay line-onay:\",\n      \"whenFollowersOnline\": \"Hen-way our-yay ollowers-fay re-ay line-onay, hey-tay ill-way how-say p-uay ere-hay.\"\n    },\n    \"login\": {\n      \"headerText\": \"Aking-tay oice-vay onversations-cay o-tay he-tay oon-may 🐷 🚀\",\n      \"featureText_1\": \"Ark-day heme-tay\",\n      \"featureText_2\": \"Pen-oay ign-say-ps-uay\",\n      \"featureText_3\": \"Ross-cay-Latform-pay Upport-say\",\n      \"featureText_4\": \"Pen-ay ource-say\",\n      \"featureText_5\": \"Ext-tay Hat-cay\",\n      \"featureText_6\": \"owered-pay y-bay oge-day (e-way so-alay ave-hay ig-pay atin-lay)\",\n      \"loginGithub\": \"Ogin-lay ith-way It-gay-Ub-hay\",\n      \"loginTwitter\": \"Ogin-lay ith-way Witter-tay\",\n      \"loginDiscord\": \"Ogin-lay ith-way Iscord-day\",\n      \"createTestUser\": \"reate-cay est-tay er-usay\"\n    },\n    \"myProfile\": {\n      \"logout\": \"og-lay ut-oay\",\n      \"probablyLoading\": \"robably-pay oading-lay...\",\n      \"voiceSettings\": \"Oice-vay Ettings-say\",\n      \"overlaySettings\": \"Erlay-ovay Ettings-say\",\n      \"soundSettings\": \"Ound-say Ettings-say\",\n      \"deleteAccount\": \"Elete-day ccount-ay\",\n      \"couldNotFindUser\": \"Orry-say, e-way ould-cay ot-nay ind-fay hat-tay er-usay\",\n      \"privacySettings\": \"Rivacy-pay Ettings-say\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Hoops-way! his-tay age-ay ot-gay ost-lay n-iay onversation-cay.\",\n      \"goHomeMessage\": \"Ot-nay o-tay orry-way. ou-yay an-cay\",\n      \"goHomeLinkText\": \"o-gay ome-hay\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Rivacy-pay Ettings-say\",\n      \"header\": \"Rivacy-pay Ettings-say\",\n      \"whispers\": { \"label\": \"Hispers-way\", \"on\": \"N-oay\", \"off\": \"FF-oay\" }\n    },\n    \"room\": {\n      \"speakers\": \"Peakers-say\",\n      \"requestingToSpeak\": \"Equesting-ray o-tay Peak-say\",\n      \"listeners\": \"Isteners-lay\",\n      \"allowAll\": \"Llow-lay ll-ay\",\n      \"allowAllConfirm\": \"Re-ay u-yay ure-say? His-tay ill-way low-alay l-alay {{count}} equesting-ray ers-usay o-tay eak-spay.\"\n    },\n    \"searchUser\": { \"search\": \"Earch-say...\" },\n    \"soundEffectSettings\": {\n      \"title\": \"Ound-say Ettings-say\",\n      \"header\": \"Ounds-say\",\n      \"playSound\": \"Lay-pay Ound-say\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Dit-eay Rofile-pay\",\n      \"followsYou\": \"Ollows-fay uo-yay\",\n      \"followers\": \"ollowers-fay\",\n      \"following\": \"ollowing-fay\",\n      \"followHim\": \"Ollow-fay\",\n      \"unfollow\": \"follow-unay\",\n      \"followingHim\": \"Ollowing-fay\",\n      \"block\": \"Lock-bay\",\n      \"unblock\": \"Block-unay\",\n      \"sendDM\": \"End-say DM-ay\",\n      \"copyProfileUrl\": \"Copy-cay rofile-pay URL-ay\",\n      \"urlCopied\": \"URL-ay opied-cay o-tay lipboard-cay\",\n      \"about\": \"Bout-ay\",\n      \"aboutSuffix\": \"\",\n      \"bot\": \"Ot-bay\",\n      \"profileTabs\": {\n        \"about\": \"Bout-ay\",\n        \"rooms\": \"Ooms-ray\",\n        \"scheduled\": \"Cheduled-say\",\n        \"recorded\": \"Ecorded-ray\",\n        \"clips\": \"Lips-cay\",\n        \"admin\": \"Min-aday\"\n      },\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"title\": \"Oice-vay Ettings-say\",\n      \"header\": \"Oice-vay Ettings-say\",\n      \"mic\": \"Ic-may:\",\n      \"permissionError\": \"O-nay ics-may ound-fay, u-oay ither-eay ave-hay one-nay ugged-play n-iay r-oay aven't-hay iven-gay his-tay ebsite-way ermission-pay.\",\n      \"refresh\": \"Efresh-ray ic-may ist-lay\",\n      \"volume\": \"Olume-vay:\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Verlay-oay Ettings-say\",\n      \"input\": {\n        \"errorMsg\": \"Lease-pay ter-enay a lid-vay pp-ay itle-tay\",\n        \"label\": \"Pp-ay itle-tay\"\n      }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Anned-bay Ers-usay\",\n      \"unban\": \"Ban-unay\",\n      \"noBans\": \"O0nay ne-oay as-hay een-bay anned-bay et-yayt\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Eave-lay Urrent-lay Oom-ray\",\n      \"confirmLeaveRoom\": \"E-ray ou-yay ure-say ou-yay ant-way o-tay eave-lay?\",\n      \"leave\": \"Eave-lay\",\n      \"inviteUsersToRoomBtn\": \"Vite-inay Ers-usay o-tay oom-ray\",\n      \"invite\": \"Vite-inay\",\n      \"toggleMuteMicBtn\": \"Oggle-tay Ute-may\",\n      \"toggleDeafMicBtn\": \"Oggle-tay Eafen-day\",\n      \"mute\": \"Ute-may\",\n      \"unmute\": \"Mute-unay\",\n      \"deafen\": \"Eafen-day\",\n      \"undeafen\": \"Deafen-unay\",\n      \"makeRoomPublicBtn\": \"Ake-may Oom-ray Ublic-pay!\",\n      \"settings\": \"Ettings-say\",\n      \"speaker\": \"Eaker-pay\",\n      \"listener\": \"Istener-lay\",\n      \"chat\": \"Hat-cay\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Our-yay evice-day s-iay urrently-cay ot-nay upportedsay. ou-yay an-cay reate-cay n-ay\",\n      \"linkText\": \"sue-say n-oay It-gay-Ub-hay\",\n      \"addSupport\": \"d-anay I ill-way ry-tay ing-adday upport-say or-fay our-yay evice-day.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Eople-pay\",\n      \"online\": \"LINE-ONAY\",\n      \"noOnline\": \"Ou-yay ave-hay 0 riends-fay line-onay ight-ray ow-nay\",\n      \"showMore\": \"how-say ore-may\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Vited-inay!\",\n      \"inviteToRoom\": \"Vite-inay o-tay oom-ray\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Essages-may\",\n      \"showMore\": \"How-say Ore-may\",\n      \"noMessages\": \"O-nay ew-nay essages-may\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Ermission-pay enied-day ying-tray o-tay ess-accay our-yay ic-may (ou-yay ay-may eed-nay o-tay o-gay to-inay rowser-bay ettings-say nd-ay eload-ray he-tay age-pay)\",\n      \"dismiss\": \"Ismiss-day\",\n      \"tryAgain\": \"Ry-tay Gain-ay\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Et-say Ebind-kay\",\n      \"listening\": \"Istening-lay...\",\n      \"toggleMuteKeybind\": \"Oggle-tay utemay eybind-kay\",\n      \"toggleDeafKeybind\": \"Oggle-tay eafen-day eybind-kay\",\n      \"toggleOverlayKeybind\": \"Oggle-tay Verlay-oay eybind-kay\",\n      \"togglePushToTalkKeybind\": \"Oggle-tay Ush-pay-O-tay-Alk-tay eybind-kay\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"O-nay dio-auay onsumer-cay or-fay ome-say eason-ray\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Coming-upay Ooms-ray\",\n      \"exploreMoreRooms\": \"Plore-exay ore-may ooms-ray\"\n    },\n    \"addToCalendar\": { \"add\": \"D-aday o-tay Alendar-cay\" },\n    \"wsKilled\": {\n      \"description\": \"Eb-wayOcket-say as-way illed-kay y-bay he-tay erver-say. His-tay Ally-usuay appens-hay hen-way ou-yay en-opay he-tay ebsite-way n-iay nother-ay ab-tay.\",\n      \"reconnect\": \"Econnect-ray\"\n    },\n    \"search\": {\n      \"placeholder\": \"Earch-say or-fay ooms-ray, ers-usay or-ay ategories-cay\",\n      \"placeholderShort\": \"Earch-say\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Rofile-pay\",\n      \"language\": \"Anguage-lay\",\n      \"reportABug\": \"Eport-ray A-ay ug-bay\",\n      \"useOldVersion\": \"Use d-olay ersion-vay\",\n      \"logOut\": {\n        \"button\": \"Og-lay Ut-oay\",\n        \"modalSubtitle\": \"Re-ay ou-yay ure-say ou-yay ant-way o-tay ogout-lay?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Ebug-day Audio\",\n        \"stopDebugger\": \"Top-say Ebugger-day\"\n      },\n      \"downloadApp\": \"Ownload-day Ap-pay\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"Oge-day-Ouse-hay Aff-stay\",\n      \"dhContributor\": \"Oge-day-Ouse-hay Ributor-contray\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Ill-fay he-tay ollowing-fay ields-fay o-tay art-stay a-ay ew-nay oom-ray\",\n        \"public\": \"Ublic-pay\",\n        \"private\": \"Rivate-pay\",\n        \"roomName\": \"Oom-ray ame-nay\",\n        \"roomDescription\": \"Oom-ray escription-day\",\n        \"descriptionError\": \"Ax-may Ength-lay 500\",\n        \"nameError\": \"Ust-may e-bay etween-bay 2 o-tay 60 aracters-chay ong-lay\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Ew-nay Oom-ray Reated-cay\",\n        \"roomInviteFrom\": \"Oom-ray Vite-inay Rom-fay\",\n        \"justStarted\": \"Hey-tay ust-jay arted-stay\",\n        \"likeToJoin\": \", Ould-way ou-yay ike-lay o-tay oin-jay?\",\n        \"inviteReceived\": \"Ou've-yay een-bay vited-inay o-tay\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Ername-usay Aken-tay\",\n        \"avatarUrlError\": \"Valid-inay age-imay\",\n        \"avatarUrlLabel\": \"It-gay-Ub-hay/Witter-tay/Iscord-day atar-avay URL-ay\",\n        \"bannerUrlLabel\": \"Witter-ay anner-bay URL-ay\",\n        \"displayNameError\": \"Ength-lay 2 o-tay 50 aracters-chay\",\n        \"displayNameLabel\": \"Isplay-day Ame-nay\",\n        \"usernameError\": \"Length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Ername-usay\",\n        \"bioError\": \"Ax-may ength-lay f-oay 160 aracters-chay\",\n        \"bioLabel\": \"Io-bay\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Re-ay ou-yay ure-say ou-yay ant-way o-tay lock-bay his-tay er-usay rom-fay oining-jay ny-ay oom-ray ou-yay ver-eay reate-cay?\",\n        \"blockUser\": \"Lock-bay er-say\",\n        \"makeMod\": \"Romote-ay o-tay od-may\",\n        \"unmod\": \"Emote=day Rom-fay Od-may\",\n        \"addAsSpeaker\": \"D-aday s-aay Eaker-spay\",\n        \"moveToListener\": \"Ove-may o-tay Istener-lay\",\n        \"unBanFromChat\": \"Ban-unay rom-fay Hat-cay\",\n        \"banFromChat\": \"An-bay rom-fay Hat-cay\",\n        \"banFromRoom\": \"An-bay rom-fay Oom-ray\",\n        \"banIPFromRoom\": \"An-bay IP-ay Rom-fay Oom-ray\",\n        \"goBackToListener\": \"O-gay Ack-bay o-tay Istener-lay\",\n        \"deleteMessage\": \"Elete-day His-tay Essage-may\",\n        \"makeRoomCreator\": \"Romote-pay o-tay Min-aday\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Equire-ray ermission-pay o-tay peak-say\",\n        \"chatCooldown\": \"Hat-cay Ooldown-cay (illiseconds-may)\",\n        \"chat\": {\n          \"label\": \"Hat-cay\",\n          \"enabled\": \"Abled-enay\",\n          \"disabled\": \"Isabled-day\",\n          \"followerOnly\": \"Ollower-fay Ly-on\"\n        },\n        \"makePublic\": \"Ake-may oom-ray ublic-pay\",\n        \"makePrivate\": \"ake-may oom-ray rivate-pay\",\n        \"renamePublic\": \"Et-say ublic-pay oom-ray ame-nay\",\n        \"renamePrivate\": \"Et-say rivate-pay oom-ray ame-nay\"\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Our-yay eed-fay\" },\n    \"scheduledRooms\": {\n      \"title\": \"Cheduled-say Ooms-ray\",\n      \"noneFound\": \"One-nay Ound-fay\",\n      \"allRooms\": \"All-ay Cheduled-say Ooms-ray\",\n      \"myRooms\": \"Y-may Cheduled-say Ooms-ray\",\n      \"scheduleRoomHeader\": \"Chedule-say Oom-ray\",\n      \"startRoom\": \"art-stat oom-ray\",\n      \"tommorow\": \"OMORROW-TAY\",\n      \"today\": \"ODAY-TAY\",\n      \"modal\": {\n        \"needsFuture\": \"eeds-nay o-tay e-bay n-iay he-tay uture-fay\",\n        \"roomName\": \"oom-ray ame-nay\",\n        \"roomDescription\": \"Escription-day\",\n        \"minLength\": \"in-may ength-lay 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"Re-ay ou-yay uresay ou-yay ant-way o-tay elete-day his-tay cheduled-say oom-ray?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Hat-cay\",\n      \"emotesSoon\": \"[Otes-enay Oon-say]\",\n      \"bannedAlert\": \"Ou-yay ave-hay een-bay anned-bay rom-fay hat-cay\",\n      \"waitAlert\": \"Ou-yay ave-hay o-tay ait-way a-ay Econd-say Efore-bay ending-say nother-ay essage-may\",\n      \"search\": \"Earch-say\",\n      \"searchResults\": \"Earch-say Esults-ray\",\n      \"recent\": \"Requently-fay Ed-usay\",\n      \"sendMessage\": \"End-say a-ay Essage-may\",\n      \"whisper\": \"Hisper-way\",\n      \"welcomeMessage\": \"Elcome-way o-tay hat-cay!\",\n      \"roomDescription\": \"Oom-ray Escription-day\",\n      \"disabled\": \"Oom-ray hat-cay as-hay een-bay isabled-day\",\n      \"messageDeletion\": {\n        \"message\": \"Essage-may\",\n        \"retracted\": \"Etracted-ray\",\n        \"deleted\": \"Eleted-day\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Ueling-fay ocket-ray\",\n      \"takingOff\": \"Aking-tay f-ofay\",\n      \"inSpace\": \"n-iay pace-say\",\n      \"approachingMoon\": \"Proaching-apay Oon-may\",\n      \"lunarDoge\": \"Unar-lay Oge-day\",\n      \"approachingSun\": \"Pproaching-apay Un-say\",\n      \"solarDoge\": \"Olar-say Oge-day\",\n      \"approachingGalaxy\": \"Pproaching-apay Alaxy-gay\",\n      \"galacticDoge\": \"Alactic-gay Oge-day\",\n      \"spottedLife\": \"Lanet-pay Ith-way Ife-lay Potted-say\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/en-PIRATE/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"get some more\",\n    \"loading\": \"loadin'\",\n    \"noUsersFound\": \"no ones 'ere\",\n    \"ok\": \"ok\",\n    \"yes\": \"aye\",\n    \"no\": \"nay\",\n    \"cancel\": \"Abandon ship!\",\n    \"save\": \"save\",\n    \"edit\": \"edit\",\n    \"delete\": \"Blow The Man Down!\",\n    \"joinRoom\": \"join cabin\",\n    \"copyLink\": \"copy link\",\n    \"copied\": \"copied\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origin Tale\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Report a Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"List o' users that are nah in a private cabin 'n ye follow.\",\n      \"currentRoom\": \"currently in:\",\n      \"startPrivateRoom\": \"start a private cabin wit' 'em\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Create Cabin\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"cabin gone, go back\",\n      \"shareRoomLink\": \"share link t' cabin\",\n      \"inviteFollowers\": \"Ye can invite yer mateys that are online:\",\n      \"whenFollowersOnline\": \"When yer scallywags are online, they will show up here.\"\n    },\n    \"login\": {\n      \"headerText\": \"Takin' voice natters t' the open sea 🌊\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Set Sail wit' with GitHub\",\n      \"loginTwitter\": \"Set Sail wit' Twitter\",\n      \"createTestUser\": \"Set Sail wit' a Test User\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Walk the plank!\",\n      \"probablyLoading\": \"prolly loadin'...\",\n      \"voiceSettings\": \"go t' voice settings\",\n      \"soundSettings\": \"go t' sound settings\",\n      \"deleteAccount\": \"Cleave Him to the Brisket!\",\n      \"overlaySettings\": \"go t' overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Blast ye! This page got lost in natter.\",\n      \"goHomeMessage\": \"Nah t' worry. Ye can\",\n      \"goHomeLinkText\": \"Davy Jones' Locker\"\n    },\n    \"room\": {\n      \"speakers\": \"Crew\",\n      \"requestingToSpeak\": \"Requestin' t' speak\",\n      \"listeners\": \"Shipmates\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Ye sure? Ye will allow all {{count}} skallywags to speak!\"\n    },\n    \"searchUser\": { \"search\": \"searchin' fer booty...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sounds\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"avast ye profile\",\n      \"followsYou\": \"follows you\",\n      \"followers\": \"scallywags\",\n      \"following\": \"hearties\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"Copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Voice Settings\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"no mics found, ye either 'ave none plugged in or haven't given this website permission.\",\n      \"refresh\": \"refresh mic list\",\n      \"volume\": \"volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"People who 'ave walked eh plank\",\n      \"unban\": \"save 'em from the o'en seas\",\n      \"noBans\": \"no one has walked the plank yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Weigh anchor\",\n      \"confirmLeaveRoom\": \"Are ye sure ye wants t' weigh anchor?\",\n      \"leave\": \"Weigh anchor\",\n      \"inviteUsersToRoomBtn\": \"Invite scallywags t' cabin\",\n      \"invite\": \"Invite scallywags\",\n      \"toggleMuteMicBtn\": \"Toggle mute microphone\",\n      \"mute\": \"Belay yer mouth!\",\n      \"unmute\": \"Unmute\",\n      \"makeRoomPublicBtn\": \"Make cabin a'ilable to eh o'en seas!\",\n      \"settings\": \"Settings\",\n      \"speaker\": \"Captain\",\n      \"listener\": \"Listener\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Yer device be currently nah supported. Ye can create an\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"'n I will try addin' support fer yer device.\"\n    },\n    \"inviteButton\": { \"invited\": \"invited\", \"inviteToRoom\": \"invite t' cabin\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permission denied trying t' access yer mic (ye may needs t' go into browser settings 'n reload the page)\",\n      \"dismiss\": \"dismiss\",\n      \"tryAgain\": \"try again\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"set keybind\",\n      \"listening\": \"listening\",\n      \"toggleMuteKeybind\": \"toggle mute keybind\",\n      \"togglePushToTalkKeybind\": \"toggle push-t'-natter keybind\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"no audio consumer fer some reason\"\n    },\n    \"addToCalendar\": { \"add\": \"Add t' Calendar\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was scuttled by the server. This usually happens when ye open the website in another tab.\",\n      \"reconnect\": \"reconnect\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"public\",\n        \"private\": \"private\",\n        \"roomName\": \"cabin name\",\n        \"roomDescription\": \"cabin description\",\n        \"descriptionError\": \"max length 500\",\n        \"nameError\": \"must be between 2 t' 60 characters long\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"New Cabin Created\",\n        \"roomInviteFrom\": \"Cabin Invite from\",\n        \"justStarted\": \"They jus' started\",\n        \"likeToJoin\": \", would ye like t' join?\",\n        \"inviteReceived\": \"ye've been invited t'\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"length 2 t' 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"length 4 t' 15 characters 'n only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"max length o' 160 characters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Are ye sure ye wants t' block this user from joinin' any cabin ye ever create?\",\n        \"blockUser\": \"Block User\",\n        \"makeMod\": \"Make Mod\",\n        \"unmod\": \"Unmod\",\n        \"addAsSpeaker\": \"Add to crew\",\n        \"moveToListener\": \"move t' shipmate\",\n        \"banFromChat\": \"Ban from chat\",\n        \"banFromRoom\": \"Ban from cabin\",\n        \"goBackToListener\": \"Go back t' shipmate\",\n        \"deleteMessage\": \"Scuttle this message\",\n        \"makeRoomCreator\": \"Make cabin cap'n\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Require permission t' speak\",\n        \"makePublic\": \"Make Cabin Public\",\n        \"makePrivate\": \"Make Cabin Private\",\n        \"renamePublic\": \"Set Public Cabin Name\",\n        \"renamePrivate\": \"Set Private Cabin Name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Scheduled Cabins\",\n      \"noneFound\": \"none found\",\n      \"allRooms\": \"all scheduled cabins\",\n      \"myRooms\": \"me scheduled cabins\",\n      \"scheduleRoomHeader\": \"Schedule Cabin\",\n      \"startRoom\": \"start cabin\",\n      \"modal\": {\n        \"needsFuture\": \"needs t' be in the future\",\n        \"roomName\": \"cabin name\",\n        \"minLength\": \"min length 2\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"Ye got banned from chat, walk the plank!\",\n      \"waitAlert\": \"Ye 'ave t' wait a second afore sendin' another message\",\n      \"search\": \"Search\",\n      \"searchResults\": \"Search Results\",\n      \"recent\": \"Frequently Used\",\n      \"sendMessage\": \"Send a Message in a bottle\",\n      \"whisper\": \"Whisper\",\n      \"welcomeMessage\": \"Ahoy t' chat!\",\n      \"roomDescription\": \"cabin description\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fuelin' rocket\",\n      \"takingOff\": \"Takin' off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approachin' moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approachin' sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approachin' galaxy\",\n      \"galacticDoge\": \"Galactic doge been spotted\",\n      \"spottedLife\": \"Planet wit' life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/eo/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"ŝarĝi pli\",\n    \"loading\": \"ŝarĝante...\",\n    \"noUsersFound\": \"neniuj uzantoj trovis\",\n    \"ok\": \"bone\",\n    \"yes\": \"jes\",\n    \"no\": \"ne\",\n    \"cancel\": \"nuligi\",\n    \"save\": \"savi\",\n    \"edit\": \"redakti\",\n    \"delete\": \"forigi\",\n    \"joinRoom\": \"aliĝi al ĉambro\",\n    \"copyLink\": \"kopiu ligon\",\n    \"copied\": \"kopiita\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Silentigita | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origina Rakonto\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Raporti Cimon\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"malpermeso\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Listo de uzantoj, kiuj ne estas en privata ĉambro kaj kiun vi sekvas.\",\n      \"currentRoom\": \"nuntempe en:\",\n      \"startPrivateRoom\": \"komencu privatan ĉambron kun ili\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Kreu Ĉambron\",\n      \"refresh\": \"Refreŝigu\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ĉambro for, reiru\",\n      \"shareRoomLink\": \"dividi ligon al ĉambro\",\n      \"inviteFollowers\": \"Vi povas inviti viajn sekvantojn interrete:\",\n      \"whenFollowersOnline\": \"Kiam viaj anoj estos interrete, ili aperos ĉi tie.\"\n    },\n    \"login\": {\n      \"headerText\": \"Prenante voĉajn konversaciojn al la luno 🚀\",\n      \"featureText_1\": \"Malhela Temo\",\n      \"featureText_2\": \"Malfermu Registriĝojn\",\n      \"featureText_3\": \"Transsistema Subteno\",\n      \"featureText_4\": \"Malferma fonto\",\n      \"featureText_5\": \"Teksta Babilejo\",\n      \"featureText_6\": \"Funkciigita de Doge\",\n      \"loginGithub\": \"ensaluti per GitHub\",\n      \"loginTwitter\": \"ensaluti per Twitter\",\n      \"createTestUser\": \"krei testuzanton\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"elsaluti\",\n      \"probablyLoading\": \"probable ŝarĝante ...\",\n      \"voiceSettings\": \"iri al voĉaj agordoj\",\n      \"soundSettings\": \"iru al sonaj agordoj\",\n      \"deleteAccount\": \"forigi konton\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ho ve! Ĉi tiu paĝo perdiĝis en konversacio\",\n      \"goHomeMessage\": \"Ne zorgu. Vi povas\",\n      \"goHomeLinkText\": \"hejmeniri\"\n    },\n    \"room\": {\n      \"speakers\": \"Parolantoj\",\n      \"requestingToSpeak\": \"Petante paroli\",\n      \"listeners\": \"Aŭskultantoj\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"serĉi...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sonoj\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"redakti profilon\",\n      \"followsYou\": \"sekvas vin\",\n      \"followers\": \"sekvantoj\",\n      \"following\": \"sekvante\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Voĉaj Agordoj\",\n      \"mic\": \"mikrofono:\",\n      \"permissionError\": \"neniuj mikrofonoj trovitaj, vi aŭ havas neniujn enŝovitajn aŭ ne donis permeson al ĉi tiu retejo.\",\n      \"refresh\": \"refreŝigi mikrofonan liston\",\n      \"volume\": \"volumo:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Malpermesitaj Uzantoj\",\n      \"unban\": \"malpermesi\",\n      \"noBans\": \"neniu estis malpermesita ankoraŭ\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Forlasu nunan ĉambron\",\n      \"confirmLeaveRoom\": \"Ĉu vi certas, ke vi volas foriri?\",\n      \"leave\": \"Foriru\",\n      \"inviteUsersToRoomBtn\": \"Invitu uzantojn al ĉambro\",\n      \"invite\": \"Inviti\",\n      \"toggleMuteMicBtn\": \"Ŝalti mutan mikrofonon\",\n      \"mute\": \"Mutulo\",\n      \"unmute\": \"Malmuta\",\n      \"makeRoomPublicBtn\": \"Publikigu ĉambron!\",\n      \"settings\": \"Agordoj\",\n      \"speaker\": \"Parolanto\",\n      \"listener\": \"Aŭskultanto\",\n      \"chat\": \"Babili\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Via aparato nuntempe ne estas subtenata. Vi povas krei\",\n      \"linkText\": \"numero ĉe GitHub\",\n      \"addSupport\": \"kaj mi provos aldoni subtenon por via aparato.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"invitita\",\n      \"inviteToRoom\": \"inviti al ĉambro\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permeso rifuzita provi aliri vian mikrofonon (eble vi bezonos eniri en retumilajn agordojn kaj reŝargi la paĝon)\",\n      \"dismiss\": \"eksigi\",\n      \"tryAgain\": \"provu denove\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"agordi klavokombinon\",\n      \"listening\": \"aŭskultanta\",\n      \"toggleMuteKeybind\": \"baskuli mutan klavaron\",\n      \"togglePushToTalkKeybind\": \"ŝalti puŝ-al-parolan klavaron\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"neniu audio-konsumanto ial\" },\n    \"addToCalendar\": { \"add\": \"Aldoni al Kalendaro\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket estis mortigita de la servilo. Ĉi tio kutime okazas kiam vi malfermas la retejon en alia langeto.\",\n      \"reconnect\": \"rekonekti\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"publika\",\n        \"private\": \"privata\",\n        \"roomName\": \"ĉambronomo\",\n        \"roomDescription\": \"ĉambra priskribo\",\n        \"descriptionError\": \"maksimuma longo 500\",\n        \"nameError\": \"devas havi inter 2 kaj 60 signojn\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nova Ĉambro Kreita\",\n        \"roomInviteFrom\": \"Ĉambro Invitu de\",\n        \"justStarted\": \"Ili ĵus komencis\",\n        \"likeToJoin\": \", ĉu vi ŝatus aliĝi?\",\n        \"inviteReceived\": \"vi estis invitita\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Uzantnomo estis prenita\",\n        \"avatarUrlError\": \"Nevalida bildo\",\n        \"avatarUrlLabel\": \"URL de Github/Twitter/Discord avataro\",\n        \"displayNameError\": \"longo 2 ĝis 50 signoj\",\n        \"displayNameLabel\": \"Vidiga Nomo\",\n        \"usernameError\": \"longo 4 ĝis 15 signoj kaj nur alfanombra/substreko\",\n        \"usernameLabel\": \"Uzantnomo\",\n        \"bioError\": \"maksimuma longo de 160 signoj\",\n        \"bioLabel\": \"Biografio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ĉu vi certas, ke vi volas malhelpi ĉi tiun uzanton aliĝi al iu ajn ĉambro, kiun vi iam kreis?\",\n        \"blockUser\": \"bloki uzanton\",\n        \"makeMod\": \"fari moderanton\",\n        \"unmod\": \"forigu moderatoron\",\n        \"addAsSpeaker\": \"aldonu kiel parolanto\",\n        \"moveToListener\": \"transloĝiĝu al aŭskultanto\",\n        \"banFromChat\": \"malpermesi babili\",\n        \"banFromRoom\": \"malpermeso de ĉambro\",\n        \"goBackToListener\": \"reiru al aŭskultanto\",\n        \"deleteMessage\": \"forigu ĉi tiun mesaĝon\",\n        \"makeRoomCreator\": \"fari ĉambron administranto\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"postulas permeson paroli\",\n        \"makePublic\": \"publikigi ĉambron\",\n        \"makePrivate\": \"privatigi ĉambron\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Planitaj Ĉambroj\",\n      \"noneFound\": \"neniu trovis\",\n      \"allRooms\": \"ĉiuj planitaj ĉambroj\",\n      \"myRooms\": \"miaj planitaj ĉambroj\",\n      \"scheduleRoomHeader\": \"Planu Ĉambron\",\n      \"startRoom\": \"komencu ĉambron\",\n      \"modal\": {\n        \"needsFuture\": \"bezonas esti en la estonteco\",\n        \"roomName\": \"ĉambronomo\",\n        \"roomDescription\": \"Priskribo\",\n        \"minLength\": \"minimuma longo 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Babili\",\n      \"emotesSoon\": \"[emotes baldaŭ]\",\n      \"bannedAlert\": \"Vi malpermesis babili\",\n      \"waitAlert\": \"Vi devas atendi sekundon antaŭ sendi alian mesaĝon\",\n      \"search\": \"Serĉu\",\n      \"searchResults\": \"Serĉrezultoj\",\n      \"recent\": \"Ofte Uzata\",\n      \"sendMessage\": \"Sendu Mesaĝon\",\n      \"whisper\": \"Flustro\",\n      \"welcomeMessage\": \"Bonvenon babili!\",\n      \"roomDescription\": \"ĉambra priskribo\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Brulaĵo raketo\",\n      \"takingOff\": \"Ekflugante\",\n      \"inSpace\": \"En la spaco\",\n      \"approachingMoon\": \"Alproksimiĝanta luno\",\n      \"lunarDoge\": \"Luna doge\",\n      \"approachingSun\": \"Alproksimiĝanta suno\",\n      \"solarDoge\": \"Suna doge\",\n      \"approachingGalaxy\": \"Alproksimiĝanta galaksio\",\n      \"galacticDoge\": \"Galaksia Doge\",\n      \"spottedLife\": \"Planedo kun vivo ekvidita\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/es/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Cargar más\",\n    \"loading\": \"Cargando...\",\n    \"noUsersFound\": \"No se han encontrado usuarios\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Si\",\n    \"no\": \"No\",\n    \"cancel\": \"Cancelar\",\n    \"save\": \"Guardar\",\n    \"edit\": \"Editar\",\n    \"delete\": \"Eliminar\",\n    \"joinRoom\": \"Unirse a la sala\",\n    \"copyLink\": \"Copiar enlace\",\n    \"copied\": \"¡Copiado!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Por favor, ten en cuenta que ejecutar DogeHouse sin permisos podría causar errores no deseados\",\n    \"copy\": \"Copiar\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Silenciado | DogeHouse\",\n    \"deafenedTitle\": \"Ensordecido  | DogeHouse\",\n    \"dashboard\": \"Panel de control\",\n    \"connectionTaken\": \"Conexión ocupada\"\n  },\n  \"footer\": {\n    \"link_1\": \"Historia de origen\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Reportar un error\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Banear\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Nombre de usuario\",\n      \"usrStaff\": \"Usuario Staff\",\n      \"usrContributions\": \"Contribuciones de Usuario\",\n      \"reason\": \"motivo\",\n      \"usernamePlaceholder\": \"nombre de usuario en el que operar\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista de usuarios que sigues y que no se encuentran en una sala privada.\",\n      \"currentRoom\": \"Actualmente en la sala:\",\n      \"startPrivateRoom\": \"Empezar una sala privada con ellos\",\n      \"title\": \"Personas\"\n    },\n    \"followList\": {\n      \"followHim\": \"Seguir\",\n      \"followingHim\": \"Siguiendo\",\n      \"title\": \"Persona\",\n      \"followingNone\": \"No estás siguiendo a nadie\",\n      \"noFollowers\": \"No hay seguidores\"\n    },\n    \"home\": {\n      \"createRoom\": \"Crear sala\",\n      \"editRoom\": \"Editar sala\",\n      \"refresh\": \"Recargar\",\n      \"desktopAlert\": \"¡Descarga la aplicación de escritorio de DogeHouse!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Sala no disponible, volver atrás\",\n      \"shareRoomLink\": \"Compartir el enlace de la sala\",\n      \"inviteFollowers\": \"Puedes invitar a tus seguidores que están en línea:\",\n      \"whenFollowersOnline\": \"Cuando tus seguidores estén en línea se mostrarán aquí.\"\n    },\n    \"login\": {\n      \"headerText\": \"Llevando conversaciones de voz a la luna 🚀\",\n      \"featureText_1\": \"Modo oscuro\",\n      \"featureText_2\": \"Registro abierto\",\n      \"featureText_3\": \"Soporte multiplataforma\",\n      \"featureText_4\": \"Código abierto\",\n      \"featureText_5\": \"Chat de texto\",\n      \"featureText_6\": \"Desarrollado por Doge\",\n      \"loginGithub\": \"Iniciar sesión con GitHub\",\n      \"loginTwitter\": \"Iniciar sesión con Twitter\",\n      \"loginDiscord\": \"Iniciar sesión con Discord\",\n      \"createTestUser\": \"Crear usuario de prueba\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Cerrar sesión\",\n      \"probablyLoading\": \"Cargando (probablemente)...\",\n      \"voiceSettings\": \"Ir a los ajustes de voz\",\n      \"overlaySettings\": \"Ir a los ajustes del overlay\",\n      \"soundSettings\": \"Ir a los ajustes de sonido\",\n      \"deleteAccount\": \"Eliminar cuenta\",\n      \"couldNotFindUser\": \"Lo siento, no pudimos encontrar ese usuario\",\n      \"privacySettings\": \"Ir a los ajustes de privacidad\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"¡Ups! Esta página parece que se ha perdido.\",\n      \"goHomeMessage\": \"No te preocupes. Puedes ir al\",\n      \"goHomeLinkText\": \"Inicio\"\n    },\n    \"room\": {\n      \"speakers\": \"Hablantes\",\n      \"requestingToSpeak\": \"Queriendo hablar\",\n      \"listeners\": \"Oyentes\",\n      \"allowAll\": \"Permitir todos\",\n      \"allowAllConfirm\": \"¿Estás seguro? Esto dejará hablar a los {{count}} usuarios que quieren hablar\"\n    },\n    \"searchUser\": { \"search\": \"Buscar...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sonidos\",\n      \"title\": \"Ajustes de sonido\",\n      \"playSound\": \"Reproducir sonido\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Editar perfil\",\n      \"followsYou\": \"Te sigue\",\n      \"followers\": \"Seguidores\",\n      \"following\": \"Siguiendo\",\n      \"followHim\": \"Seguir\",\n      \"unfollow\": \"Dejar de seguir\",\n      \"followingHim\": \"Siguiendo\",\n      \"copyProfileUrl\": \"Copiar URL del perfil\",\n      \"urlCopied\": \"URL copiado en el portapapeles\",\n      \"about\": \"Acerca de\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Acerca de\",\n        \"rooms\": \"Salas\",\n        \"scheduled\": \"Programado\",\n        \"recorded\": \"Grabado\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Bloquear\",\n      \"unblock\": \"Desbloquear\",\n      \"sendDM\": \"Enviar PM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"Este usuario te ha bloqueado\",\n        \"default\": \"¡Ups! No se ha podido cargar este perfil\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ajustes de voz\",\n      \"mic\": \"Micrófono:\",\n      \"permissionError\": \"No se encontraron micrófonos; puede que no esté conectado o no hayas otorgado los permisos correctos.\",\n      \"refresh\": \"Recargar la lista de micrófonos\",\n      \"volume\": \"Volúmen:\",\n      \"title\": \"Configuración de voz\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Opciones del overlay\",\n      \"input\": {\n        \"errorMsg\": \"Ese nombre de aplicación no es válido\",\n        \"label\": \"Introduce el nombre de la aplicación\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Comenzando a descargar...\",\n      \"failed\": \"No se pudo descargar automáticamente. Vuelve a intentarlo más tarde.\",\n      \"visit_gh\": \"Visitar Github Releases\",\n      \"prompt\": \"Haz clic en el botón de abajo para iniciar la descarga\",\n      \"download_now\": \"Descargar ahora\",\n      \"download_for\": \"Descargar para %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Configuración de privacidad\",\n      \"header\": \"Configuración de privacidad\",\n      \"whispers\": { \"label\": \"Susurros\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Tus Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Información de bot\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerar\",\n      \"reveal\": \"Click aquí para mostrar tu API Key\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Usuarios baneados\",\n      \"unban\": \"Desbanear\",\n      \"noBans\": \"Nadie ha sido baneado todavía\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Abandonar la sala actual\",\n      \"confirmLeaveRoom\": \"¿Estás seguro de que quieres abandonar la sala?\",\n      \"leave\": \"Abandonar\",\n      \"inviteUsersToRoomBtn\": \"Invitar usuarios a la sala\",\n      \"invite\": \"Invitar\",\n      \"toggleMuteMicBtn\": \"Alternar silenciar micrófono\",\n      \"mute\": \"Desactivar micrófono\",\n      \"unmute\": \"Activar micrófono\",\n      \"makeRoomPublicBtn\": \"Hacer la sala pública\",\n      \"settings\": \"Ajustes\",\n      \"speaker\": \"Hablante\",\n      \"listener\": \"Oyente\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Alternar ensordecimiento\",\n      \"deafen\": \"Ensordecer\",\n      \"undeafen\": \"Desactivar ensordecimiento\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Actualmente tu dispositivo no es compatible. Puedes crear un\",\n      \"linkText\": \"issue en GitHub\",\n      \"addSupport\": \"e intentaremos agregar soporte para tu dispositivo.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Personas\",\n      \"online\": \"EN LÍNEA\",\n      \"noOnline\": \"Ninguno de tus amigos en línea\",\n      \"showMore\": \"Mostrar más\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"¡Invitado!\",\n      \"inviteToRoom\": \"Invitar a la sala\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permiso denegado al intentar acceder a tu micrófono (es posible que tengas que acceder a la configuración del navegador y recargar la página)\",\n      \"dismiss\": \"Descartar\",\n      \"tryAgain\": \"Intentar de nuevo\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Configurar combinación de teclas\",\n      \"listening\": \"Escuchando\",\n      \"toggleMuteKeybind\": \"Combinación de teclas para apagar/encender tu micrófono\",\n      \"toggleOverlayKeybind\": \"Combinación de teclas para alternar el overlay\",\n      \"togglePushToTalkKeybind\": \"Combinación de teclas para activar/desactivar tocar-para-hablar\",\n      \"toggleDeafKeybind\": \"Alternar combinación de teclas para ensordecer\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Por alguna razón no hay ningún consumidor de audio\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Próximas salas\",\n      \"exploreMoreRooms\": \"Explorar nuevas salas\"\n    },\n    \"addToCalendar\": { \"add\": \"Añadir al calendario\" },\n    \"wsKilled\": {\n      \"description\": \"El Websocket fue eliminado por el servidor. Esto suele suceder cuando abres el sitio web en otra pestaña.\",\n      \"reconnect\": \"Reconectar\"\n    },\n    \"search\": {\n      \"placeholder\": \"Buscar salas, usuarios o categorías\",\n      \"placeholderShort\": \"Buscar\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Perfil\",\n      \"language\": \"Idioma\",\n      \"reportABug\": \"Reportar un problema\",\n      \"useOldVersion\": \"Usar versión antigua\",\n      \"logOut\": {\n        \"button\": \"Cerrar sesión\",\n        \"modalSubtitle\": \"¿Estás seguro de que quieres cerrar sesión?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Depurar audio\",\n        \"stopDebugger\": \"Detener depurador\"\n      },\n      \"downloadApp\": \"Descargar App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Completa este formulario para crear una nueva sala\",\n        \"public\": \"Pública\",\n        \"private\": \"Privada\",\n        \"roomName\": \"Nombre de la sala\",\n        \"roomDescription\": \"Descripción de la sala\",\n        \"descriptionError\": \"Longitud máxima de 500 caracteres\",\n        \"nameError\": \"Debe contener entre 2 y 60 caracteres\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nueva sala creada\",\n        \"roomInviteFrom\": \"Invitación a la sala de\",\n        \"justStarted\": \"Acaban de comenzar\",\n        \"likeToJoin\": \", ¿Te gustaría unirte?\",\n        \"inviteReceived\": \"Has sido invitado a\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Nombre de usuario ya ocupado\",\n        \"avatarUrlError\": \"Imagen inválida\",\n        \"avatarUrlLabel\": \"URL del avatar de Github/Twitter/Discord\",\n        \"displayNameError\": \"Longitud de 2 a 50 caracteres\",\n        \"displayNameLabel\": \"Nombre a mostrar\",\n        \"usernameError\": \"Longitud de 4 a 15 caracteres y solo puede contener caracteres alfanuméricos/guión bajo\",\n        \"usernameLabel\": \"Nombre de usuario\",\n        \"bioError\": \"Longitud máxima de 160 caracteres\",\n        \"bioLabel\": \"Biografía\",\n        \"bannerUrlLabel\": \"URL del banner de Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"¿Estás seguro de que quieres bloquear a este usuario para que no pueda unirse a las salas que crees?\",\n        \"blockUser\": \"Bloquear usuario\",\n        \"makeMod\": \"Convertir en moderador\",\n        \"unmod\": \"Quitar moderador\",\n        \"addAsSpeaker\": \"Añadir como hablante\",\n        \"moveToListener\": \"Mover a oyente\",\n        \"unBanFromChat\": \"Desbanear del chat\",\n        \"banFromChat\": \"Banear del chat\",\n        \"banFromRoom\": \"Banear de la sala\",\n        \"goBackToListener\": \"Volver a ser oyente\",\n        \"deleteMessage\": \"Eliminar mensaje\",\n        \"makeRoomCreator\": \"Hacer administrador de la sala\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Solicitar permiso para hablar\",\n        \"makePublic\": \"Hacer la sala pública\",\n        \"makePrivate\": \"Hacer la sala privada\",\n        \"renamePublic\": \"Configurar el nombre de la sala pública\",\n        \"renamePrivate\": \"Configurar el nombre de la sala privada\",\n        \"chatDisabled\": \"Deshabilitar chat\",\n        \"chatCooldown\": \"Chat Cooldown (milisegundos)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Habilitado\",\n          \"disabled\": \"Deshabilitado\",\n          \"followerOnly\": \"Solo seguidores\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Ese nombre de usuario ya esta siendo usado\",\n        \"subtitle\": \"Rellena los datos para crear tu bot\",\n        \"title\": \"Crear Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Mensajes\",\n      \"showMore\": \"Mostrar más\",\n      \"noMessages\": \"No hay más mensajes\"\n    }\n  },\n  \"modules\": {\n    \"feed\": { \"yourFeed\": \"Tu inicio\" },\n    \"scheduledRooms\": {\n      \"title\": \"Salas programadas\",\n      \"noneFound\": \"No se ha encontrado ninguna\",\n      \"allRooms\": \"Todas las salas programadas\",\n      \"myRooms\": \"Mis salas programadas\",\n      \"scheduleRoomHeader\": \"Programar una sala\",\n      \"startRoom\": \"Iniciar una sala\",\n      \"modal\": {\n        \"needsFuture\": \"Debe estar en el futuro\",\n        \"roomName\": \"Nombre de la sala\",\n        \"roomDescription\": \"Descripción\",\n        \"minLength\": \"La longitud mínima es de 2 caracteres\"\n      },\n      \"tommorow\": \"MAÑANA\",\n      \"today\": \"HOY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"¿Estás seguro de que quieres eliminar esta sala programada?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes pronto]\",\n      \"bannedAlert\": \"Fuiste baneado del chat\",\n      \"waitAlert\": \"Tienes que esperar un segundo antes de enviar otro mensaje.\",\n      \"search\": \"Buscar\",\n      \"searchResults\": \"Resultados de la búsqueda\",\n      \"recent\": \"Usados frecuentemente\",\n      \"sendMessage\": \"Enviar un mensaje\",\n      \"whisper\": \"Susurrar\",\n      \"welcomeMessage\": \"¡Bienvenido al chat!\",\n      \"roomDescription\": \"Descripción de la sala\",\n      \"disabled\": \"El chat ha sido deshabilitado\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Llenando el cohete de combustible\",\n      \"takingOff\": \"Despegando\",\n      \"inSpace\": \"En el espacio\",\n      \"approachingMoon\": \"Acercándose a la luna\",\n      \"lunarDoge\": \"Doge lunar\",\n      \"approachingSun\": \"Acercándose al sol\",\n      \"solarDoge\": \"Doge solar\",\n      \"approachingGalaxy\": \"Acercándose a la galaxia\",\n      \"galacticDoge\": \"Doge galáctico\",\n      \"spottedLife\": \"Vida descubierta en el planeta\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/et/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"laadi rohkem\",\n    \"loading\": \"laadib...\",\n    \"noUsersFound\": \"ühtegi kasutajat ei leitud\",\n    \"ok\": \"ok\",\n    \"yes\": \"jah\",\n    \"no\": \"ei\",\n    \"cancel\": \"tühista\",\n    \"save\": \"salvesta\",\n    \"edit\": \"muuda\",\n    \"delete\": \"kustuta\",\n    \"joinRoom\": \"liitu ruumiga\",\n    \"copyLink\": \"kopeeri link\",\n    \"copied\": \"kopeeritud\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Pange tähele, et DogeHouse'i käitamine ilma juurdepääsulubadeta võib põhjustada soovimatuid vigu\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Vaigistatud | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ajalugu\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Teata veast\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"bänni\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Kasutajad, keda jälgid ja kes ei ole privaatses ruumis\",\n      \"currentRoom\": \"praegu sees:\",\n      \"startPrivateRoom\": \"loo nendega privaatne ruum\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"jälgi\",\n      \"followingHim\": \"jälgid\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Loo ruum\",\n      \"refresh\": \"Värskenda\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ruum läinud, mine tagasi\",\n      \"shareRoomLink\": \"jaga link ruumi\",\n      \"inviteFollowers\": \"Saad kutsuda oma jälgijaid, kes on hetkel aktiivsed:\",\n      \"whenFollowersOnline\": \"Teie aktiivsed jälgijad ilmuvad siia\"\n    },\n    \"login\": {\n      \"headerText\": \"Viime häälevestlused kuule 🚀\",\n      \"featureText_1\": \"Tume teema\",\n      \"featureText_2\": \"Avatud registreerimised\",\n      \"featureText_3\": \"Platvormidevaheline toetus\",\n      \"featureText_4\": \"Avatud lähtekoodiga\",\n      \"featureText_5\": \"Tekstivestlus\",\n      \"featureText_6\": \"Töötab Doge'i jõul\",\n      \"loginGithub\": \"Logi GitHub'iga\",\n      \"loginTwitter\": \"Logi Twitter'iga\",\n      \"createTestUser\": \"Loo test-kasutaja\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"logi välja\",\n      \"probablyLoading\": \"ilmselt laadib...\",\n      \"voiceSettings\": \"mine häälesätetesse\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"soundSettings\": \"mine helisätetesse\",\n      \"deleteAccount\": \"kustuta konto\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oih! See leht läks vestluses kaduma.\",\n      \"goHomeMessage\": \"Pole vaja muretseda. Sa saad\",\n      \"goHomeLinkText\": \"minna koju\"\n    },\n    \"room\": {\n      \"speakers\": \"Kõnelejad\",\n      \"requestingToSpeak\": \"Kõnelemise taotlemine\",\n      \"listeners\": \"Kuulajad\",\n      \"allowAll\": \"Luba kõik\",\n      \"allowAllConfirm\": \"Oled sa kindel? See tegevus lubab kõigil {{count}} taotlejal rääkida\"\n    },\n    \"searchUser\": { \"search\": \"otsi...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Helid\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"muuda profiili\",\n      \"followsYou\": \"jälgib sind\",\n      \"followers\": \"jälgijaid\",\n      \"following\": \"jälgib\",\n      \"followHim\": \"jälgi\",\n      \"followingHim\": \"jälgid\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Häälesätted\",\n      \"mic\": \"mikrofon:\",\n      \"permissionError\": \"ühtegi mikrofoni ei leitud; mikrofone ei ole ühendatud või veebilehel puudub õigus neid kasutada.\",\n      \"refresh\": \"värskenda mikrofonide loendit\",\n      \"volume\": \"helitugevus:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Ülekatte Sätted\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bännitud kasutajad\",\n      \"unban\": \"tühista bänn\",\n      \"noBans\": \"kedagi pole veel bännitud\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Lahku ruumist\",\n      \"confirmLeaveRoom\": \"Kas oled kindel, et soovid ruumist lahkuda?\",\n      \"leave\": \"Lahku\",\n      \"inviteUsersToRoomBtn\": \"Kutsu ruumi kasutajaid\",\n      \"invite\": \"Kutsu\",\n      \"toggleMuteMicBtn\": \"Mikrofoni lüliti\",\n      \"mute\": \"Vaigista mikrofon\",\n      \"unmute\": \"Ava mikrofon\",\n      \"makeRoomPublicBtn\": \"Tee ruum avalikuks!\",\n      \"settings\": \"Sätted\",\n      \"speaker\": \"Kõneleja\",\n      \"listener\": \"Kuulaja\",\n      \"chat\": \"Vestlus\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Sinu seadet ei toetata veel. Saad luua\",\n      \"linkText\": \"GitHub'is veateate\",\n      \"addSupport\": \"ning üritame lisada toe teie seadmele.\"\n    },\n    \"inviteButton\": { \"invited\": \"kutsutud\", \"inviteToRoom\": \"kutsu ruumi\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Ligipääs mikrofonile keelatud (võib-olla peate vaatama brauseri sätteid ning lehte värskendama)\",\n      \"dismiss\": \"tühista\",\n      \"tryAgain\": \"proovi uuesti\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"määra klahvistik\",\n      \"listening\": \"kuulab\",\n      \"toggleMuteKeybind\": \"vaigistamise klahvistik\",\n      \"togglePushToTalkKeybind\": \"push-to-talk klahvistik\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"pole ühtegi audiotarbijat mingil põhjusel\"\n    },\n    \"addToCalendar\": { \"add\": \"Lisa kalendrisse\" },\n    \"wsKilled\": {\n      \"description\": \"Veebipesa suleti serveri poolt. See juhtub tavaliselt siis, kui avad veebilehe teisel vahelehel.\",\n      \"reconnect\": \"ühenda uuesti\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"avalik\",\n        \"private\": \"privaatne\",\n        \"roomName\": \"ruumi nimi\",\n        \"roomDescription\": \"ruumi kirjeldus\",\n        \"descriptionError\": \"max. pikkus 500\",\n        \"nameError\": \"peab olema 2 kuni 60 tähemärki pikk\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Uus ruum loodud\",\n        \"roomInviteFrom\": \"Ruumi kutse kasutajalt\",\n        \"justStarted\": \"Nad just alustasid\",\n        \"likeToJoin\": \", kas tahad liituda?\",\n        \"inviteReceived\": \"sind on kutsutud ruumi\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"kasutajanimi on juba kasutusel\",\n        \"avatarUrlError\": \"Vigane pilt\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatari URL\",\n        \"displayNameError\": \"2 kuni 50 tähemärki pikk\",\n        \"displayNameLabel\": \"Kuvatav nimi\",\n        \"usernameError\": \"4 kuni 15 tähemärki pikk ja ainult tähenumbriline/alakriips\",\n        \"usernameLabel\": \"Kasutajanimi\",\n        \"bioError\": \"maksimum pikkus: 160 tähemärki\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Kas tahad kindlasti selle kasutaja liitumist teie ruumidesse keelata?\",\n        \"blockUser\": \"Blokeeri kasutaja\",\n        \"makeMod\": \"Tee moderaatoriks\",\n        \"unmod\": \"eemalda moderaatoriõigus\",\n        \"addAsSpeaker\": \"lisa kõnelejana\",\n        \"moveToListener\": \"liiguta kuulajatesse\",\n        \"banFromChat\": \"bänni vestlusest\",\n        \"banFromRoom\": \"bänni ruumist\",\n        \"goBackToListener\": \"mine tagasi kuulajatesse\",\n        \"deleteMessage\": \"kustuta sõnum\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"nõua luba rääkimiseks\",\n        \"makePublic\": \"tee ruum avalikuks\",\n        \"makePrivate\": \"tee ruum privaatseks\",\n        \"renamePublic\": \"Säti avaliku ruumi nimi\",\n        \"renamePrivate\": \"Säti privaatse ruumi nimi\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Ajastatud ruumid\",\n      \"noneFound\": \"ühtegi ei leitud\",\n      \"allRooms\": \"kõik ajastatud ruumid\",\n      \"myRooms\": \"minu ajastatud ruumid\",\n      \"scheduleRoomHeader\": \"Ajasta ruum\",\n      \"startRoom\": \"Alusta ruum\",\n      \"modal\": {\n        \"needsFuture\": \"peab olema tulevikus\",\n        \"roomName\": \"ruumi nimi\",\n        \"roomDescription\": \"kirjeldus\",\n        \"minLength\": \"min. pikkus 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Vestlus\",\n      \"emotesSoon\": \"[emojid varsti]\",\n      \"bannedAlert\": \"Sind bänniti vestlusest\",\n      \"waitAlert\": \"Pead ootama sekundi enne, kui saad sõnumit saata\",\n      \"search\": \"Otsing\",\n      \"searchResults\": \"Otsingu tulemused\",\n      \"recent\": \"Enim kasutatud\",\n      \"sendMessage\": \"Saada sõnum\",\n      \"whisper\": \"Sosista\",\n      \"welcomeMessage\": \"Tere tulemast vestlusesse!\",\n      \"roomDescription\": \"ruumi kirjeldus\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Tankimas raketti\",\n      \"takingOff\": \"Startimas\",\n      \"inSpace\": \"Kosmoses\",\n      \"approachingMoon\": \"Lähenemas Kuule\",\n      \"lunarDoge\": \"Kuu-doge\",\n      \"approachingSun\": \"Lähenemas Päikesele\",\n      \"solarDoge\": \"Päikese-Doge\",\n      \"approachingGalaxy\": \"Lähenemas galaktikale\",\n      \"galacticDoge\": \"Galaktiline-Doge\",\n      \"spottedLife\": \"Planeet eluga\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/eu/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"gehiago kargatu\",\n    \"loading\": \"kargatzen\",\n    \"noUsersFound\": \"ez dira erabiltzaileak aurkitu\",\n    \"ok\": \"ados\",\n    \"yes\": \"bai\",\n    \"no\": \"ez\",\n    \"cancel\": \"deuseztatu\",\n    \"save\": \"gorde\",\n    \"edit\": \"editatu\",\n    \"delete\": \"ezabatu\",\n    \"joinRoom\": \"gelara sartu\",\n    \"copyLink\": \"esteka kopiatu\",\n    \"copied\": \"kopiatuta\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Isilik | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Jatorri historia\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Errore bat jakinarazi\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"blokeatu\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"List of users that are not in a private room and you follow.\",\n      \"currentRoom\": \"orain:\",\n      \"startPrivateRoom\": \"hasi saio pribatu bat\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Follow\",\n      \"followingHim\": \"Following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Gela Sortu\",\n      \"editRoom\": \"Edit Room\",\n      \"refresh\": \"Refresh\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"gela ez dago erabilgarri\",\n      \"shareRoomLink\": \"gelaren esteka partekatu\",\n      \"inviteFollowers\": \"You can invite your followers that are online:\",\n      \"whenFollowersOnline\": \"Zure jarraitzaileak linean daudenean hemen agertuko dira.\"\n    },\n    \"login\": {\n      \"headerText\": \"Elkarrizketak ilargira eramaten 🚀\",\n      \"featureText_1\": \"Modu iluna\",\n      \"featureText_2\": \"Erregistro irekia\",\n      \"featureText_3\": \"Soporte multiplataforma\",\n      \"featureText_4\": \"Kode irekia\",\n      \"featureText_5\": \"Testu txat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"saioa hasi GitHub-en bidez\",\n      \"loginTwitter\": \"saioa hasi Twitter-en bidez\",\n      \"loginDiscord\": \"saioa hasi Discord-en bidez\",\n      \"createTestUser\": \"froga erabiltzailea sortu\"\n    },\n    \"myProfile\": {\n      \"logout\": \"saioa itxi\",\n      \"probablyLoading\": \"Segur aski kargatzen...\",\n      \"voiceSettings\": \"ahots aukerak\",\n      \"overlaySettings\": \"Overlay Settings\",\n      \"soundSettings\": \"soinu aukerak\",\n      \"deleteAccount\": \"kontua ezabatu\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\n      \"goHomeMessage\": \"Not to worry. You can\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"room\": {\n      \"speakers\": \"mintzatzaileak\",\n      \"requestingToSpeak\": \"hitza eskatzen\",\n      \"listeners\": \"entzuleak\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"bilatu...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Soinuak\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"editatu profila\",\n      \"followsYou\": \"jarraitzen zaitu\",\n      \"followers\": \"jarraitzaileak\",\n      \"following\": \"jarraitzen\",\n      \"followHim\": \"Follow\",\n      \"unfollow\": \"Unfollow\",\n      \"followingHim\": \"Following\",\n      \"copyProfileUrl\": \"Copy Profile URL\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ahots aukerak\",\n      \"mic\": \"mikrofonoa:\",\n      \"permissionError\": \"ez dira mikrofonorik aurkitu, you either have none plugged in or haven't given this website permission.\",\n      \"refresh\": \"mikrofono zerrenda berriro kargatu\",\n      \"volume\": \"ozentasuna:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter a valid app title\",\n        \"label\": \"App title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Erabiltzaile blokeatutak\",\n      \"unban\": \"Desblokeatu\",\n      \"noBans\": \"No one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Gela utzi\",\n      \"confirmLeaveRoom\": \"gela uzten ari zara, zihur zaude?\",\n      \"leave\": \"Utzi\",\n      \"inviteUsersToRoomBtn\": \"Erabiltzaileak gonbidatu\",\n      \"invite\": \"Gonbidatu\",\n      \"toggleMuteMicBtn\": \"Aldatu mikrofonoaren isiltasuna\",\n      \"mute\": \"Mikrofonoa desaktibatu\",\n      \"unmute\": \"Mikrofonoa aktibatu\",\n      \"makeRoomPublicBtn\": \"Gela publikoa sortu!\",\n      \"settings\": \"Aukerak\",\n      \"speaker\": \"Mintzatzaile\",\n      \"listener\": \"Entzule\",\n      \"chat\": \"Txat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Gaur egun zure gailua ez da bateragarri. Sortu\",\n      \"linkText\": \"arazo bat GitHub-en\",\n      \"addSupport\": \"eta saiatuko naiz zure gailuari soportea ematen.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"gonbidatuta\",\n      \"inviteToRoom\": \"gelara gonbidatu\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Mikrofonoa atzitzeko baimenak ukatutak (you may need to go into browser settings and reload the page)\",\n      \"dismiss\": \"baztertu\",\n      \"tryAgain\": \"Saiatu berriro\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"konfiguratu tekla konbinazioa\",\n      \"listening\": \"entzuten...\",\n      \"toggleMuteKeybind\": \"Piztu/itzali isiltzeko tekla konbinazioa\",\n      \"toggleOverlayKeybind\": \"Toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"Piztu/itzali sakatu-hitz-egiteko tekla konbinazioa\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"No audio consumer for some reason\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore more rooms\"\n    },\n    \"addToCalendar\": { \"add\": \"Egutegira gehitu\" },\n    \"wsKilled\": {\n      \"description\": \"Serbitzariak websocket-a ezabatu du. Hau gertatzen da web-orrialdea beste fitxa batean irekeitzen denean.\",\n      \"reconnect\": \"berriro konektatu\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profila\",\n      \"language\": \"Hizkuntza\",\n      \"reportABug\": \"Errore bat jakinarazi\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Saioa itxi\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Fill the following fields to start a new room\",\n        \"public\": \"publikoa\",\n        \"private\": \"pribatua\",\n        \"roomName\": \"gelaren izena\",\n        \"roomDescription\": \"gelaren deskribapena\",\n        \"descriptionError\": \"luzera maximoa: 500 karaktere\",\n        \"nameError\": \"2-60 karaktere izan behar ditu\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Gela berria sortuta\",\n        \"roomInviteFrom\": \"Room Invite from\",\n        \"justStarted\": \"They just started\",\n        \"likeToJoin\": \", would you like to join?\",\n        \"inviteReceived\": \"you've been invited to\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"erabiltzaile izena hartuta\",\n        \"avatarUrlError\": \"Irudi baliogabea\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"luzera: 2-50 karaktere\",\n        \"displayNameLabel\": \"Erakusteko izena\",\n        \"usernameError\": \"luzera: 4-15 karaktere alfanumerikoak/azpi-gidioa\",\n        \"usernameLabel\": \"Erabiltzaile izena\",\n        \"bioError\": \"luzera maximoa: 160 karaktere\",\n        \"bioLabel\": \"Biografia\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Are you sure you want to block this user from joining any room you ever create?\",\n        \"blockUser\": \"erabiltzailea blokeatu\",\n        \"makeMod\": \"moderatzaile bihurtu\",\n        \"unmod\": \"moderatzailea kendu\",\n        \"addAsSpeaker\": \"mintzatzaile bihurtu\",\n        \"moveToListener\": \"entzule bihurtu\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banFromChat\": \"Txata blokeatu\",\n        \"banFromRoom\": \"gela blokeatu\",\n        \"goBackToListener\": \"bueltatu entzule izatera\",\n        \"deleteMessage\": \"mezua ezabatu\",\n        \"makeRoomCreator\": \"Promote to Admin\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"baimenak behar dituzu hitz egiteko\",\n        \"makePublic\": \"gela publikoa bihurtu\",\n        \"makePrivate\": \"gela pribatua bihurtu\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Your feed\" },\n    \"scheduledRooms\": {\n      \"title\": \"Gela programatuak\",\n      \"noneFound\": \"ez da bakar bat ere aurkitu\",\n      \"allRooms\": \"Gela programatu guztiak\",\n      \"myRooms\": \"nire gela programatuak\",\n      \"scheduleRoomHeader\": \"Gela programatu\",\n      \"startRoom\": \"gela hasi\",\n      \"modal\": {\n        \"needsFuture\": \"Etorkizunean izen behar da\",\n        \"roomName\": \"gelaren izena\",\n        \"roomDescription\": \"deskribapena\",\n        \"minLength\": \"luzera minimoa: 2 karaktere\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Txat\",\n      \"emotesSoon\": \"[emoteak berehala]\",\n      \"bannedAlert\": \"Txatean blokeatuta zaude\",\n      \"waitAlert\": \"Segundu bat itxaron behar duzu beste mezu bat bidali baino lehen.\",\n      \"search\": \"Bilatu\",\n      \"searchResults\": \"Bilaketaren emaitzak\",\n      \"recent\": \"Maiz Erabiliak\",\n      \"sendMessage\": \"Mezua bidali\",\n      \"whisper\": \"Xuxurlatu\",\n      \"welcomeMessage\": \"Ongi etorri Txatera!\",\n      \"roomDescription\": \"gelaren deskribapena\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Suziria errekargatzen\",\n      \"takingOff\": \"Aireratzen\",\n      \"inSpace\": \"Espazioan\",\n      \"approachingMoon\": \"Ilargira iristen\",\n      \"lunarDoge\": \"Ilargi doge\",\n      \"approachingSun\": \"Eguzkira iristen\",\n      \"solarDoge\": \"Eguzki doge\",\n      \"approachingGalaxy\": \"Galaxiara iristen\",\n      \"galacticDoge\": \"Doge galaktikoa\",\n      \"spottedLife\": \"Planet with life spotted\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/fa/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"نمایش بیشتر\",\n    \"loading\": \"در حال بارگذاری...\",\n    \"noUsersFound\": \"کاربری پیدا نشد!\",\n    \"ok\": \"تایید\",\n    \"yes\": \"بله\",\n    \"no\": \"خیر\",\n    \"cancel\": \"لغو\",\n    \"save\": \"ذخیره\",\n    \"edit\": \"ویرایش\",\n    \"delete\": \"حذف\",\n    \"joinRoom\": \"ورود به اتاق\",\n    \"copyLink\": \"کپی کردن لینک\",\n    \"copied\": \"کپی انجام شد!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"لطفاً توجه داشته باشید که اجرای DogeHouse بدون مجوزهای دسترسی ممکن است باعث خطاهای ناخواسته شود.\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"dashboard\": \"داشبورد\",\n    \"connectionTaken\": \"اتصال گرفته شده است.\",\n    \"mutedTitle\": \"بی صدا | DogeHouse\",\n    \"deafenedTitle\": \"ناشنوا | DogeHouse\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"داستان مبدا\",\n    \"link_2\": \"دیسکورد\",\n    \"link_3\": \"گزارش مشکل\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"منع کردن\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"download\": {\n      \"prompt\": \"برنامه DogeHouse Desktop را بارگیری کنید\",\n      \"download_for\": \"بارگیری کنید برای %platform% (%ext%)\",\n      \"failed\": \"شناسایی سیستم عامل امکان پذیر نیست ، لطفاً بعداً دوباره امتحان کنید یا از نسخه های GitHub بازدید کنید\",\n      \"visit_gh\": \"از نسخه های GitHub بازدید کنید\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"لیست کاربرانی که در یک اتاق خصوصی نیستند و آنها را دنبال می کنید.\",\n      \"currentRoom\": \"در حال حاضر در:\",\n      \"startPrivateRoom\": \"با آنها یک اتاق خصوصی راه اندازی کنید\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"دنبال کردن\",\n      \"followingHim\": \"دنبال شده\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"اتاق جدید\",\n      \"editRoom\": \"ویرایش اتاق\",\n      \"refresh\": \"بارگیری مجدد\",\n      \"desktopAlert\": \"همین امروز برنامه دسک تاپ DogeHouse را بارگیری کنید\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"اتاق بسته شد؛ برگرد به عقب\",\n      \"shareRoomLink\": \"لینک اتاق را به اشتراک بگذارید\",\n      \"inviteFollowers\": \"شما می توانید دنبال کنندگان خود را که آنلاین هستند دعوت کنید:\",\n      \"whenFollowersOnline\": \"وقتی دنبال کنندگان شما آنلاین باشند ، در اینجا نشان داده می شوند.\"\n    },\n    \"login\": {\n      \"headerText\": \"مکالمات صوتی را به ماه منتقل کنید 🚀\",\n      \"featureText_1\": \"تم تاریک\",\n      \"featureText_2\": \"ورود به سیستم را باز کنید\",\n      \"featureText_3\": \"پشتیبانی از کراس پلتفرم\",\n      \"featureText_4\": \"متن باز\",\n      \"featureText_5\": \"چت\",\n      \"featureText_6\": \"قدرت گرقته از Doge\",\n      \"loginGithub\": \"با GitHub وارد شوید\",\n      \"loginTwitter\": \"با توییتر وارد شوید\",\n      \"loginDiscord\": \"با Discord وارد شوید\",\n      \"createTestUser\": \"کاربر آزمایشی ایجاد کنید\"\n    },\n    \"myProfile\": {\n      \"logout\": \"خروج\",\n      \"probablyLoading\": \"احتمالاً در حال بارگیری ...\",\n      \"voiceSettings\": \"تنظیمات صدا\",\n      \"overlaySettings\": \"تنظیمات همپوشانی\",\n      \"soundSettings\": \"تنظیمات صدا\",\n      \"deleteAccount\": \"حذف حساب کاربری\",\n      \"couldNotFindUser\": \"متأسفیم ، ما آن کاربر را پیدا نکردیم\",\n      \"privacySettings\": \"تنظیمات حریم خصوصی\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"اوه! این صفحه در مکالمه گم شده است.\",\n      \"goHomeMessage\": \"نگران نباشید. تو می توانی\",\n      \"goHomeLinkText\": \"به صفحه اصلی بروی\"\n    },\n    \"room\": {\n      \"speakers\": \"سخنرانان\",\n      \"requestingToSpeak\": \"درخواست صحبت کردن\",\n      \"listeners\": \"شنوندگان\",\n      \"allowAll\": \"اجازه دادن به همه\",\n      \"allowAllConfirm\": \"مطمئنی؟ با این کار به همه {{count}} درخواست کنندگان اجازه صحبت می دهید\"\n    },\n    \"searchUser\": { \"search\": \"جستجو کردن...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"صدا\",\n      \"title\": \"تنظیمات صدا\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"ویرایش نمایه\",\n      \"followsYou\": \"شما را دنبال می کند\",\n      \"followers\": \"دنبال کنندگان\",\n      \"following\": \"دنبال شده\",\n      \"followHim\": \"دنبال کردن\",\n      \"unfollow\": \"دنبال نشود\",\n      \"followingHim\": \"دنبال شده\",\n      \"copyProfileUrl\": \"کپی کردن آدرس نمایه\",\n      \"urlCopied\": \"آدرس در حافظه موقت کپی شد\",\n      \"about\": \"درباره\",\n      \"bot\": \"بات\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"تنظیمات صدا\",\n      \"mic\": \"میکروفون:\",\n      \"permissionError\": \"هیچ میکروفونی یافت نشد،شما میکروفون را متصل نکرده و یا به این وبسایت اجازه نداده اید.\",\n      \"refresh\": \"بارگذاری مجدد لیست میکروفون\",\n      \"volume\": \"بلندی صدا\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"تنظیمات همپوشانی\",\n      \"input\": {\n        \"errorMsg\": \"لطفاً یک عنوان معتبر برای برنامه وارد کنید.\",\n        \"label\": \"عنوان برنامه\"\n      }\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"کاربران مسدود شده\",\n      \"unban\": \"در آوردن از مسدود\",\n      \"noBans\": \"هنوز هیچ شخصی مسدود نشده است\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"اتاق فعلی را ترک کنید\",\n      \"confirmLeaveRoom\": \"آیا مطمئن هستی که می خواهی ترک کنی؟\",\n      \"leave\": \"ترک کردن\",\n      \"inviteUsersToRoomBtn\": \"کاربران را به اتاق دعوت کنید\",\n      \"invite\": \"دعوت\",\n      \"toggleMuteMicBtn\": \"قطع صدا\",\n      \"toggleDeafMicBtn\": \"ضامن ناشنوا\",\n      \"mute\": \"بی صدا\",\n      \"unmute\": \"وصل کردن صدا\",\n      \"deafen\": \"بی صدا\",\n      \"undeafen\": \"وصل کردن صدا\",\n      \"makeRoomPublicBtn\": \"اتاق را عمومی کنید!\",\n      \"settings\": \"تنظیمات\",\n      \"speaker\": \"سخنگو\",\n      \"listener\": \"شنونده\",\n      \"chat\": \"چت\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"دستگاه شما در حال حاضر پشتیبانی نمی شود. شما می توانید یک\",\n      \"linkText\": \"مشکل در GitHub درست کنید\",\n      \"addSupport\": \"و من سعی می کنم پشتیبانی از دستگاه شما را اضافه کنم.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"مردم\",\n      \"online\": \"آنلاین\",\n      \"noOnline\": \"شما در حال حاضر 0 دوست آنلاین دارید\",\n      \"showMore\": \"اطلاعات بیشتر\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"دعوت شد!\",\n      \"inviteToRoom\": \"به اتاق دعوت کنید\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"مجوز تلاش برای دسترسی به میکروفن شما رد شد (ممکن است لازم باشد وارد تنظیمات مرورگر شوید و صفحه را دوباره بارگیری کنید)\",\n      \"dismiss\": \"رد\",\n      \"tryAgain\": \"دوباره امتحان کنید\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Keybind را تنظیم کنید\",\n      \"listening\": \"شنیدن\",\n      \"toggleMuteKeybind\": \"دکمه قطع کردن صدا\",\n      \"toggleDeafKeybind\": \"دکمه نشنیدن\",\n      \"toggleOverlayKeybind\": \"دکمه تنظیمات همپوشانی\",\n      \"togglePushToTalkKeybind\": \"دکمه فشردن برای صحبت کردن\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"به دلایلی مصرف کننده صدا وجود ندارد\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"اتاق های آینده\",\n      \"exploreMoreRooms\": \"در اتاق های بیشتری کاوش کنید\"\n    },\n    \"addToCalendar\": { \"add\": \"افزودن به تقویم\" },\n    \"wsKilled\": {\n      \"description\": \"اتصال به سرور قطع شد. ممکن است برنامه را در صفحه ای دیگر باز کرده اید\",\n      \"reconnect\": \"اتصال دوباره\"\n    },\n    \"search\": {\n      \"placeholder\": \"اتاق ها ، کاربران یا دسته ها را جستجو کنید\",\n      \"placeholderShort\": \"جستجو کنید\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"مشخصات\",\n      \"language\": \"زبان\",\n      \"reportABug\": \"گزارش یک اشکال\",\n      \"useOldVersion\": \"از نسخه قدیمی استفاده کنید\",\n      \"logOut\": {\n        \"button\": \"خروج\",\n        \"modalSubtitle\": \"آیا برای خارج شدن مطمئن هستید؟\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"اشکال زدایی صدا\",\n        \"stopDebugger\": \"اشکال زدایی را متوقف کنید\"\n      },\n      \"downloadApp\": \"بارگیری برنامه\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"برای راه اندازی یک اتاق جدید ، قسمت های زیر را پر کنید\",\n        \"public\": \"عمومی\",\n        \"private\": \"خصوصی\",\n        \"roomName\": \"نام اتاق\",\n        \"roomDescription\": \"توضیحات اتاق\",\n        \"descriptionError\": \"حداکثر طول 500\",\n        \"nameError\": \"باید بین 2 تا 60 نویسه باشد\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"اتاق جدید ایجاد شد\",\n        \"roomInviteFrom\": \"اتاق دعوت از\",\n        \"justStarted\": \"آنها تازه شروع کردند\",\n        \"likeToJoin\": \"، آیا دوست دارید بپیوندید؟\",\n        \"inviteReceived\": \"شما دعوت شده اید به\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"نام کاربری گرفته شده\",\n        \"avatarUrlError\": \"تصویر نامعتبر است\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar URL\",\n        \"bannerUrlLabel\": \"Twitter banner URL\",\n        \"displayNameError\": \"طول 2 تا 50 نویسه\",\n        \"displayNameLabel\": \"نمایش نام\",\n        \"usernameError\": \"طول 4 تا 15 حرف و فقط حروف الفبا / زیر خط\",\n        \"usernameLabel\": \"نام کاربری\",\n        \"bioError\": \"حداکثر طول 160 نویسه\",\n        \"bioLabel\": \"بیو\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"آیا مطمئن هستید که می خواهید از ورود این کاربر به هر اتاقی که ایجاد کرده اید جلوگیری کنید؟\",\n        \"blockUser\": \"کاربر را مسدود کنید\",\n        \"makeMod\": \"به Mod ارتقا دهید\",\n        \"unmod\": \"تنزل مقام از Mod\",\n        \"addAsSpeaker\": \"به عنوان سخنگو اضافه کنید\",\n        \"moveToListener\": \"انتقال به شنوندگان\",\n        \"unBanFromChat\": \"لغو ممنوعیت از چت\",\n        \"banFromChat\": \"ممنوع کردن از چت\",\n        \"banFromRoom\": \"ممنوعیت ورود به اتاق\",\n        \"goBackToListener\": \"بازگشت به شنونده\",\n        \"deleteMessage\": \"این پیام را حذف کنید\",\n        \"makeRoomCreator\": \"به مدیر ارتقا دهید\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"نیاز به گرفتن اجازه برای صحبت دارد\",\n        \"chatDisabled\": \"چت را غیرفعال کنید\",\n        \"makePublic\": \"اتاق را عمومی کنید\",\n        \"makePrivate\": \"اتاق را خصوصی کنید\",\n        \"renamePublic\": \"نام اتاق عمومی را تنظیم کنید\",\n        \"renamePrivate\": \"نام اتاق خصوصی را تنظیم کنید\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"فید شما\" },\n    \"scheduledRooms\": {\n      \"title\": \"اتاق های برنامه ریزی شده\",\n      \"noneFound\": \"هیچ کدام پیدا نشد\",\n      \"allRooms\": \"تمام اتاقهای برنامه ریزی شده\",\n      \"myRooms\": \"اتاقهای برنامه ریزی شده من\",\n      \"scheduleRoomHeader\": \"زمانبندی اتاق\",\n      \"startRoom\": \"شروع اتاق\",\n      \"tommorow\": \"فردا\",\n      \"today\": \"امروز\",\n      \"modal\": {\n        \"needsFuture\": \"باید در آینده باشد\",\n        \"roomName\": \"نام اتاق\",\n        \"roomDescription\": \"شرح\",\n        \"minLength\": \"حداقل طول 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"آیا مطمئن هستید که می خواهید این اتاق برنامه ریزی شده را حذف کنید؟\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"چت\",\n      \"emotesSoon\": \"[ایموت ها به زودی]\",\n      \"bannedAlert\": \"شما از چت منع شده اید\",\n      \"waitAlert\": \"قبل از ارسال پیام دیگری باید لحظه ای صبر کنید\",\n      \"search\": \"جستجو\",\n      \"searchResults\": \"نتایج جستجو\",\n      \"recent\": \"اغلب استفاده می شود\",\n      \"sendMessage\": \"ارسال یک پیام\",\n      \"whisper\": \"پچ پچ\",\n      \"welcomeMessage\": \"به چت خوش آمدید!\",\n      \"roomDescription\": \"توضیحات اتاق\",\n      \"disabled\": \"گفتگوی اتاق غیرفعال شده است\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"سوخت موشک\",\n      \"takingOff\": \"در حال بلند شدن\",\n      \"inSpace\": \"در فضا\",\n      \"approachingMoon\": \"نزدیک شدن به ماه\",\n      \"lunarDoge\": \"دوج قمری\",\n      \"approachingSun\": \"نزدیک شدن به خورشید\",\n      \"solarDoge\": \"دوج خورشیدی\",\n      \"approachingGalaxy\": \"نزدیک شدن به کهکشان\",\n      \"galacticDoge\": \"دوج کهکشانی\",\n      \"spottedLife\": \"سیاره ای که در آن حیات وجود دارد پیدا شد.\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/fi/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Lataa lisää\",\n    \"loading\": \"Ladataan...\",\n    \"noUsersFound\": \"Käyttäjiä ei löytynyt\",\n    \"ok\": \"OK\",\n    \"yes\": \"Kyllä\",\n    \"no\": \"Ei\",\n    \"cancel\": \"Peruuta\",\n    \"save\": \"Tallenna\",\n    \"edit\": \"Muokkaa\",\n    \"delete\": \"Poista\",\n    \"joinRoom\": \"Liity huoneeseen\",\n    \"copyLink\": \"Kopioi linkki\",\n    \"copied\": \"Kopioitu\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Huomaa, että käyttämällä DogeHousea ilman joitakin selaimen käyttöoikeuksia voi aiheuttaa ei-toivottuja virheitä\",\n    \"copy\": \"Kopioi\",\n    \"error\": \"Virhe\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Mykistetty | DogeHouse\",\n    \"deafenedTitle\": \"Hiljennetty | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Yhteys katkaistu\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Tarina\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Ilmoita Ongelmasta\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Estä\",\n      \"userStaffandContrib\": \"Käyttäjän henkilökuntatila ja kontribuutiot\",\n      \"staff\": \"Henkilökunta: \",\n      \"contributions\": \"Kontribuutiot\",\n      \"username\": \"Käyttäjätunnus\",\n      \"usrStaff\": \"Käyttäjän henkilökuntatila\",\n      \"usrContributions\": \"Käyttäjän kontribuutiot\",\n      \"reason\": \"syy\",\n      \"usernamePlaceholder\": \"käyttäjätunnus, johon suoritetaan toiminnot\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista seuraamistasi käyttäjistä, jotka eivät ole yksityisessä huoneessa.\",\n      \"currentRoom\": \"Paikalla:\",\n      \"startPrivateRoom\": \"Luo yksityinen huone heidän kanssaan\",\n      \"title\": \"Henkilöt\"\n    },\n    \"followList\": {\n      \"followHim\": \"Seuraa\",\n      \"followingHim\": \"Seurannassa\",\n      \"title\": \"Henkilöt\",\n      \"followingNone\": \"Ei seuraa ketään\",\n      \"noFollowers\": \"Ei seuraajia\"\n    },\n    \"home\": {\n      \"createRoom\": \"Luo huone\",\n      \"refresh\": \"Päivitä\",\n      \"editRoom\": \"Muokkaa huonetta\",\n      \"desktopAlert\": \"Lataa DogeHouse-työpöytäsovellus tänään!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Huone poistettu, palaa takaisin\",\n      \"shareRoomLink\": \"Jaa linkki huoneeseen\",\n      \"inviteFollowers\": \"Voit kutsua paikalla olevia seuraajiasi:\",\n      \"whenFollowersOnline\": \"Kun seuraajasi on paikalla, löydät heidät täältä.\"\n    },\n    \"login\": {\n      \"headerText\": \"Äänikeskustelu kuuhun 🚀\",\n      \"featureText_1\": \"Tumma teema\",\n      \"featureText_2\": \"Avoin kirjautuminen\",\n      \"featureText_3\": \"Alustariippumaton\",\n      \"featureText_4\": \"Avoin lähdekoodi\",\n      \"featureText_5\": \"Tekstikanavat\",\n      \"featureText_6\": \"Dogen voimalla\",\n      \"loginGithub\": \"Kirjaudu GitHubilla\",\n      \"loginTwitter\": \"Kirjaudu Twitterillä\",\n      \"loginDiscord\": \"Kirjaudu Discordilla\",\n      \"createTestUser\": \"Luo testikäyttäjä\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Kirjaudu ulos\",\n      \"probablyLoading\": \"Ladataan...\",\n      \"voiceSettings\": \"Puheasetukset\",\n      \"soundSettings\": \"Ääniasetukset\",\n      \"overlaySettings\": \"Overlay-asetukset\",\n      \"deleteAccount\": \"Poista tili\",\n      \"couldNotFindUser\": \"Käyttäjää ei löytynyt\",\n      \"privacySettings\": \"Yksityisyysasetukset\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Hups! Tämä sivu katosi keskusteluun.\",\n      \"goHomeMessage\": \"Älä huoli, voit\",\n      \"goHomeLinkText\": \"palata alkuun\"\n    },\n    \"room\": {\n      \"speakers\": \"Puhujat\",\n      \"requestingToSpeak\": \"Pyytävät puheoikeutta\",\n      \"listeners\": \"Kuuntelijat\",\n      \"allowAll\": \"Hyväksy kaikki\",\n      \"allowAllConfirm\": \"Oletko varma? Tämä antaa kaikille {{count}} pyytäville käyttäjille puheoikeuden\"\n    },\n    \"searchUser\": { \"search\": \"Hae...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Äänet\",\n      \"title\": \"Ääniasetukset\",\n      \"playSound\": \"Toista ääni\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Muokkaa profiilia\",\n      \"followsYou\": \"Seuraa sinua\",\n      \"followers\": \"Seuraajaa\",\n      \"following\": \"Seurannassa\",\n      \"followHim\": \"Seuraa\",\n      \"followingHim\": \"Seurannassa\",\n      \"copyProfileUrl\": \"Kopioi profiilin URL-osoite\",\n      \"urlCopied\": \"Osoite kopioitu leikepöydälle\",\n      \"unfollow\": \"Lopeta seuraaminen\",\n      \"about\": \"Tietoa käyttäjästä\",\n      \"bot\": \"Botti\",\n      \"profileTabs\": {\n        \"about\": \"Tietoa käyttäjästä\",\n        \"rooms\": \"Huoneet\",\n        \"scheduled\": \"Ajoitetut\",\n        \"recorded\": \"Tallennetut\",\n        \"clips\": \"Klipit\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Estä\",\n      \"unblock\": \"Poista esto\",\n      \"sendDM\": \"Lähetä yksityisviesti\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"Tämä käyttäjä esti sinut.\",\n        \"default\": \"Hups! Tätä käyttäjää ei voitu ladata.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Puheasetukset\",\n      \"mic\": \"Mikrofoni:\",\n      \"permissionError\": \"Yhtään mikrofonia ei löytynyt. joko yhtään ei ole kytketty tai et ole antanut sivustolle lupaa käyttää mikrofonia.\",\n      \"refresh\": \"Päivitä lista\",\n      \"volume\": \"Äänenvoimakkuus:\",\n      \"title\": \"Puheasetukset\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay-asetukset\",\n      \"input\": {\n        \"errorMsg\": \"Epäkelpo sovelluksen nimi\",\n        \"label\": \"Syötä sovelluksen nimi\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Aloitetaan lataaminen...\",\n      \"failed\": \"Automaattinen lataus epäonnistui, yritä uudelleen myöhemmin\",\n      \"visit_gh\": \"Näe Github-julkaisut\",\n      \"prompt\": \"Aloita lataaminen klikkaamalla alla olevaa painiketta\",\n      \"download_now\": \"Lataa nyt\",\n      \"download_for\": \"Lataa %platform%ille (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Yksityisyysasetukset\",\n      \"header\": \"Yksityisyysasetukset\",\n      \"whispers\": { \"label\": \"Kuiskaukset\", \"on\": \"Käytössä\", \"off\": \"Pois käytöstä\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Bottisi\",\n      \"bots\": \"Botit\",\n      \"title\": \"Botin tiedot\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Uusi\",\n      \"reveal\": \"Paljasta ApiKey klikkaamalla\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Estetyt käyttäjät\",\n      \"unban\": \"Poista esto\",\n      \"noBans\": \"Ketään ei ole estetty\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Poistu huoneesta\",\n      \"confirmLeaveRoom\": \"Haluatko varmasti poistua huoneesta?\",\n      \"leave\": \"Poistu\",\n      \"inviteUsersToRoomBtn\": \"Kutsu käyttäjiä huoneeseen\",\n      \"invite\": \"Kutsu\",\n      \"toggleMuteMicBtn\": \"Kytke mikrofonin mykistys\",\n      \"mute\": \"Mykistä\",\n      \"unmute\": \"Poista mykistys\",\n      \"makeRoomPublicBtn\": \"Tee huoneesta julkinen\",\n      \"settings\": \"Asetukset\",\n      \"speaker\": \"Puhuja\",\n      \"listener\": \"Kuuntelija\",\n      \"chat\": \"Chatti\",\n      \"toggleDeafMicBtn\": \"Kytke hiljennys\",\n      \"deafen\": \"Hiljennä\",\n      \"undeafen\": \"Poista hiljennys\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Käyttämääsi laitetta ei tueta. Voit luoda\",\n      \"linkText\": \"ilmoituksen ongelmasta GitHubiin\",\n      \"addSupport\": \"ja yritän lisätä tuen laitteellesi.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Kutsuttu\",\n      \"inviteToRoom\": \"Kutsu huoneeseen\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Mikrofonin käyttö estetty (sinun täytyy mahdollisesti muuttaa selaimesi asetuksia ja päivittää sivu)\",\n      \"dismiss\": \"Ohita\",\n      \"tryAgain\": \"Yritä uudelleen\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Aseta pikanäppäin\",\n      \"listening\": \"Odotetaan näppäintä\",\n      \"toggleMuteKeybind\": \"Mikrofonin mykistys -pikanäppäin\",\n      \"togglePushToTalkKeybind\": \"Push-to-talk-pikanäppäin\",\n      \"toggleOverlayKeybind\": \"Overlay-pikanäppäin\",\n      \"toggleDeafKeybind\": \"Hiljennys-pikanäppäin\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Ei äänen ulostulolaitteita\" },\n    \"addToCalendar\": { \"add\": \"Lisää kalenteriin\" },\n    \"wsKilled\": {\n      \"description\": \"Palvelin katkaisi yhteyden. Tämä johtuu yleensä sivuston avaamisesta useampaan välilehteen.\",\n      \"reconnect\": \"Yhdistä uudelleen\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Julkinen\",\n        \"private\": \"Yksityinen\",\n        \"roomName\": \"Huoneen nimi\",\n        \"roomDescription\": \"Huoneen kuvaus\",\n        \"descriptionError\": \"Maksimipituus 500 merkkiä\",\n        \"nameError\": \"Tulee olla 2-60 merkkiä pitkä\",\n        \"subtitle\": \"Luo uusi huone täyttämällä seuraavat kentät\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Uusi huone luotu\",\n        \"roomInviteFrom\": \"Kutsu huoneeseen käyttäjältä\",\n        \"justStarted\": \"Huone on juuri aloitettu\",\n        \"likeToJoin\": \", Haluaisitko liittyä mukaan?\",\n        \"inviteReceived\": \"Sinut on kutsuttu liittymään\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Käyttäjätunnus on varattu\",\n        \"avatarUrlError\": \"Virheellinen kuva\",\n        \"avatarUrlLabel\": \"Github-/Twitter-avatarin URL-osoite\",\n        \"displayNameError\": \"Pituus 2-50 merkkiä\",\n        \"displayNameLabel\": \"Nimimerkki\",\n        \"usernameError\": \"Pituus 4-15 merkkiä ja vain aakkosia, numeroita tai alaviivoja\",\n        \"usernameLabel\": \"Käyttäjätunnus\",\n        \"bioError\": \"Maksimipituus 160 merkkiä\",\n        \"bioLabel\": \"Kuvaus\",\n        \"bannerUrlLabel\": \"Twitter-bannerikuvan URL-osoite\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Haluatko varmasti estää tämän käyttäjän liittymistä yhteenkään huoneeseesi?\",\n        \"blockUser\": \"Estä käyttäjä\",\n        \"makeMod\": \"Lisää ylläpitäjäksi\",\n        \"unmod\": \"Poista ylläpitäjä\",\n        \"addAsSpeaker\": \"Lisää puhujaksi\",\n        \"moveToListener\": \"Siirrä kuuntelijaksi\",\n        \"banFromChat\": \"Estä chatista\",\n        \"banFromRoom\": \"Estä huoneesta\",\n        \"goBackToListener\": \"Palaa kuuntelijaksi\",\n        \"deleteMessage\": \"Poista viesti\",\n        \"makeRoomCreator\": \"Ylennä huoneen ylläpitäjäksi\",\n        \"unBanFromChat\": \"Poista esto chatista\",\n        \"banIPFromRoom\": \"Estä IP-osoite huoneesta\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Puhuminen vaatii oikeuden\",\n        \"makePublic\": \"Tee huoneesta julkinen\",\n        \"makePrivate\": \"Tee huoneesta yksityinen\",\n        \"renamePublic\": \"Aseta julkinen huoneen nimi\",\n        \"renamePrivate\": \"Aseta yksityinen huoneen nimi\",\n        \"chatDisabled\": \"Poista chatti käytöstä\",\n        \"chatCooldown\": \"Chatin cooldown-aika (millisekunteina)\",\n        \"chat\": {\n          \"label\": \"Chatti\",\n          \"enabled\": \"Käytössä\",\n          \"disabled\": \"Pois käytöstä\",\n          \"followerOnly\": \"Vain seuraajat\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Käyttätunnus varattu\",\n        \"subtitle\": \"Luo botti täyttämällä seuraavat kentät\",\n        \"title\": \"Luo botti\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Henkilöt\",\n      \"online\": \"PAIKALLA\",\n      \"noOnline\": \"Sinulla on tällä hetkellä 0 kaveria paikalla\",\n      \"showMore\": \"Näytä lisää\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Tulevat huoneet\",\n      \"exploreMoreRooms\": \"Etsi lisää huoneita\"\n    },\n    \"search\": {\n      \"placeholder\": \"Hae huoneita, käyttäjiä tai kategorioita\",\n      \"placeholderShort\": \"Hae\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profiili\",\n      \"language\": \"Kieli\",\n      \"reportABug\": \"Ilmoita ongelmasta\",\n      \"useOldVersion\": \"Käytä vanhaa versioita\",\n      \"logOut\": {\n        \"button\": \"Kirjaudu ulos\",\n        \"modalSubtitle\": \"Haluatko varmasti kirjautua ulos?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Testaa äänet\",\n        \"stopDebugger\": \"Pysäytä debuggeri\"\n      },\n      \"downloadApp\": \"Lataa sovellus\",\n      \"developer\": \"Kehittäjäasetukset\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHousen henkilökunta\",\n      \"dhContributor\": \"DogeHouse-avustaja\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Viestit\",\n      \"showMore\": \"Näytä lisää\",\n      \"noMessages\": \"Ei uusia viestejä\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Ajoitetut huoneet\",\n      \"noneFound\": \"Yhtään huonetta ei löytynyt\",\n      \"allRooms\": \"Kaikki ajoitetut huoneet\",\n      \"myRooms\": \"Minun ajoitetut huoneet\",\n      \"scheduleRoomHeader\": \"Ajoita huone\",\n      \"startRoom\": \"Aloita huone\",\n      \"modal\": {\n        \"needsFuture\": \"Täytyy olla tulevaisuudessa\",\n        \"roomName\": \"Huoneen nimi\",\n        \"minLength\": \"Minimipituus 2\",\n        \"roomDescription\": \"Huoneen kuvaus\"\n      },\n      \"tommorow\": \"HUOMENNA\",\n      \"today\": \"TÄNÄÄN\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Haluatko varmasti poistaa tämän ajoitetun huoneen?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chatti\",\n      \"emotesSoon\": \"[emojit tulossa]\",\n      \"bannedAlert\": \"Sinut on estetty huoneesta\",\n      \"waitAlert\": \"Odota hetki ennen uuden viestin lähettämistä\",\n      \"search\": \"Hae\",\n      \"searchResults\": \"Hakutulokset\",\n      \"recent\": \"Usein käytetyt\",\n      \"sendMessage\": \"Lähetä viesti\",\n      \"whisper\": \"Kuiskaus\",\n      \"welcomeMessage\": \"Tervetuloa chattiin!\",\n      \"roomDescription\": \"Huoneen kuvaus\",\n      \"disabled\": \"Chatti poistettiin käytöstä\",\n      \"messageDeletion\": {\n        \"message\": \"viesti\",\n        \"retracted\": \"peruttu\",\n        \"deleted\": \"poistettu\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Tankkaamassa rakettia\",\n      \"takingOff\": \"Nousemassa lentoon\",\n      \"inSpace\": \"Avaruudessa\",\n      \"approachingMoon\": \"Lähestymässä kuuta\",\n      \"lunarDoge\": \"Kuu-doge\",\n      \"approachingSun\": \"Lähestymässä aurinkoa\",\n      \"solarDoge\": \"Aurinko-doge\",\n      \"approachingGalaxy\": \"Lähestymässä galaksia\",\n      \"galacticDoge\": \"Galaktinen Doge\",\n      \"spottedLife\": \"Havaittiin planeetta, jossa on elämää\"\n    },\n    \"feed\": { \"yourFeed\": \"Syötteesi\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/fr/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Charger Plus\",\n    \"loading\": \"Chargement...\",\n    \"noUsersFound\": \"Aucun utilisateur trouvé!\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Oui\",\n    \"no\": \"Non\",\n    \"cancel\": \"Annuler\",\n    \"save\": \"Sauvegarder\",\n    \"edit\": \"Éditer\",\n    \"delete\": \"Supprimer\",\n    \"joinRoom\": \"Rejoindre la Salle\",\n    \"copyLink\": \"Copier le Lien\",\n    \"copied\": \"Copié!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Veuillez noter que l'exécution de DogeHouse sans autorisations d'accessibilité peut provoquer des erreurs indésirables\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muet | DogeHouse\",\n    \"deafenedTitle\": \"Assourdi | DogeHouse\",\n    \"dashboard\": \"Tableau de board\",\n    \"connectionTaken\": \"Lien Pris\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Histoire d'origine\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Signaler un problème\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Bannir\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Liste des utilisateurs qui ne sont pas dans une salle privée et que vous suivez\",\n      \"currentRoom\": \"Actuellement dans :\",\n      \"startPrivateRoom\": \"Lancer une salle privée avec eux\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"S'abonner\",\n      \"followingHim\": \"Abonné\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Créer une salle\",\n      \"editRoom\": \"Editer la salle\",\n      \"refresh\": \"Actualiser\",\n      \"desktopAlert\": \"Téléchargez l'application DogeHouse aujourd'hui!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Cette salle n'existe plus; revenez en arrière\",\n      \"shareRoomLink\": \"Partager le lien de la salle\",\n      \"inviteFollowers\": \"Vous pouvez inviter vos abonnés en ligne :\",\n      \"whenFollowersOnline\": \"Quand vos abonnés seront en ligne, ils apparaîtront ici.\"\n    },\n    \"login\": {\n      \"headerText\": \"Prends les conversations vocales jusqu'à la lune 🚀\",\n      \"featureText_1\": \"Thème sombre\",\n      \"featureText_2\": \"Inscriptions ouvertes\",\n      \"featureText_3\": \"Support Multi-plateforme\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Tchat Textuel\",\n      \"featureText_6\": \"Alimenté par Doge\",\n      \"loginGithub\": \"Se connecter avec GitHub\",\n      \"loginTwitter\": \"Se connecter avec Twitter\",\n      \"loginDiscord\": \"Se connecter avec Discord\",\n      \"createTestUser\": \"Créer un utilisateur de test\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Se déconnecter\",\n      \"probablyLoading\": \"Probablement en train de charger...\",\n      \"voiceSettings\": \"Paramètres Vocaux\",\n      \"overlaySettings\": \"Paramètres de l'overlay\",\n      \"soundSettings\": \"Paramètres Audio\",\n      \"deleteAccount\": \"Supprimer le Compte\",\n      \"couldNotFindUser\": \"Désolé, nous ne parvenons pas à trouver cet utilisateur\",\n      \"privacySettings\": \"Paramètres de confidentialité\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oups ! Cette page s'est perdue dans la conversation.\",\n      \"goHomeMessage\": \"Ne vous inquiétez pas. Vous pouvez\",\n      \"goHomeLinkText\": \"Retourner à l'accueil\"\n    },\n    \"room\": {\n      \"speakers\": \"Interlocuteurs\",\n      \"requestingToSpeak\": \"Demande la parole\",\n      \"listeners\": \"Auditeurs\",\n      \"allowAll\": \"Tout autoriser\",\n      \"allowAllConfirm\": \"Êtes-vous sûr? Cela autorisera {{count}} utilisateurs en attente de parler\"\n    },\n    \"searchUser\": { \"search\": \"recherche...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sons\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Éditer le profil\",\n      \"followsYou\": \"Vous suit\",\n      \"followers\": \"Abonnés\",\n      \"following\": \"Abonnements\",\n      \"followHim\": \"S'abonner\",\n      \"followingHim\": \"Abonné\",\n      \"copyProfileUrl\": \"Copier le lien du profil\",\n      \"urlCopied\": \"Lien copié dans le presse-papier\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"À propos de\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"À propos\",\n        \"rooms\": \"salles\",\n        \"scheduled\": \"Programmé\",\n        \"recorded\": \"enregistré\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Paramètres vocaux\",\n      \"mic\": \"Microphone :\",\n      \"permissionError\": \"Pas de microphone trouvé, soit vous n'en avez pas branché un soit vous n'avez pas donné la permission au site de l'utiliser.\",\n      \"refresh\": \"Rafraîchir la liste des microphones\",\n      \"volume\": \"Volume :\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Paramètres de l'Overlay\",\n      \"input\": {\n        \"errorMsg\": \"Veuillez rentrer un titre valide\",\n        \"label\": \"Titre de l'application\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Téléchargement entamé...\",\n      \"failed\": \"Téléchargement interrompu, essayer de nouveau plus tard\",\n      \"visit_gh\": \"Visiter toutes les versions sur Github\",\n      \"prompt\": \"Cliquer sur le bouton pour commencer le Téléchargement\",\n      \"download_now\": \"Télécharger maintenant\",\n      \"download_for\": \"Télécharger pour la %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Utilisateurs bannis\",\n      \"unban\": \"Débannir\",\n      \"noBans\": \"Personne n'a été banni pour l'instant\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Quitter la salle actuelle\",\n      \"confirmLeaveRoom\": \"Êtes-vous sûr de vouloir quitter?\",\n      \"leave\": \"Quitter\",\n      \"inviteUsersToRoomBtn\": \"Inviter des utilisateurs dans la salle\",\n      \"invite\": \"Inviter\",\n      \"toggleMuteMicBtn\": \"Activer / Désactiver le microphone\",\n      \"mute\": \"Rendre muet\",\n      \"unmute\": \"Rétablir la voix\",\n      \"makeRoomPublicBtn\": \"Rendre la salle publique!\",\n      \"settings\": \"Paramètres\",\n      \"speaker\": \"Interlocuteur\",\n      \"listener\": \"Auditeur\",\n      \"chat\": \"Tchat\",\n      \"toggleDeafMicBtn\": \"Activer / Désactiver le son\",\n      \"deafen\": \"Couper le son\",\n      \"undeafen\": \"Rétablier le son\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Votre système n'est pas encore supporté. Vous pouvez créer une\",\n      \"linkText\": \"issue sur GitHub\",\n      \"addSupport\": \"et j'essaierai d'ajouter le support pour votre système.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Personnes\",\n      \"online\": \"EN LIGNE\",\n      \"noOnline\": \"Vous n'avez pas d'amis en ligne pour le moment\",\n      \"showMore\": \"Voir plus\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invité!\",\n      \"inviteToRoom\": \"Inviter dans la salle\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permission refusée en essayant d'accéder à votre microphone (vous devriez peut être vérifier vos paramètres de navigateur et recharger la page)\",\n      \"dismiss\": \"Fermer\",\n      \"tryAgain\": \"Réessayer\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Changer les raccourcis clavier\",\n      \"listening\": \"Écoutant...\",\n      \"toggleMuteKeybind\": \"Activer / Désactiver le raccourci clavier pour rendre muet\",\n      \"toggleOverlayKeybind\": \"Activer / Désactiver le raccourci clavier pour l'overlay\",\n      \"togglePushToTalkKeybind\": \"Activer / Désactiver le raccourci clavier appuyer-pour-parler\",\n      \"toggleDeafKeybind\": \"Activer / Désactiver le raccourci clavier pour couper le son\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Pas d'audio pour une raison inconnue\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Prochaines salles\",\n      \"exploreMoreRooms\": \"Explorer plus de salles\"\n    },\n    \"addToCalendar\": { \"add\": \"Ajouter au Calendrier\" },\n    \"wsKilled\": {\n      \"description\": \"Le WebSocket a été fermé par le serveur. Cela arrive souvent quand vous ouvrez le site dans une autre fenêtre.\",\n      \"reconnect\": \"Se reconnecter\"\n    },\n    \"search\": {\n      \"placeholder\": \"Rechercher des salles, utilisateurs ou catégories\",\n      \"placeholderShort\": \"Recherche\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Signaler un problème\",\n      \"useOldVersion\": \"Utiliser l'ancienne version\",\n      \"logOut\": {\n        \"button\": \"Se déconnecter\",\n        \"modalSubtitle\": \"Êtes vous sur de vouloir vous déconnecter ?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Débugger l'audio\",\n        \"stopDebugger\": \"Arreter le débuggage\"\n      },\n      \"downloadApp\": \"Télécharger l'Application\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Remplis les champs suivants pour créer\",\n        \"public\": \"Publique\",\n        \"private\": \"Privée\",\n        \"roomName\": \"Nom de la salle\",\n        \"roomDescription\": \"Description de la salle\",\n        \"descriptionError\": \"Taille maximum de 500 caractères\",\n        \"nameError\": \"Ce champ doit être entre 2 et 60 caractères de longueur\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nouvelle salle créée\",\n        \"roomInviteFrom\": \"Invitation à la salle de\",\n        \"justStarted\": \"Viennent de commencer\",\n        \"likeToJoin\": \", voulez-vous les rejoindre ?\",\n        \"inviteReceived\": \"vous avez été invité à\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Pseudo déjà pris\",\n        \"avatarUrlError\": \"Image invalide\",\n        \"avatarUrlLabel\": \"URL d'avatar Github/Twitter/Discord\",\n        \"displayNameError\": \"Longueur de 2 à 50 caractères\",\n        \"displayNameLabel\": \"Nom affiché\",\n        \"usernameError\": \"Longueur de 4 à 15 caractères et ne doit contenir que des caractères alphanumériques ou des tirets du bas\",\n        \"usernameLabel\": \"Pseudo\",\n        \"bioError\": \"Longueur maximum de 160 caractères\",\n        \"bioLabel\": \"Biographie\",\n        \"bannerUrlLabel\": \"URL de bannière Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Êtes-vous sûr de vouloir bloquer cet utilisateur de toutes vos salles ?\",\n        \"blockUser\": \"Bloquer l'utilisateur\",\n        \"makeMod\": \"Rendre modérateur\",\n        \"unmod\": \"Enlever les permissions\",\n        \"addAsSpeaker\": \"Ajouter en tant qu'interlocuteur\",\n        \"moveToListener\": \"Rendre auditeur\",\n        \"unBanFromChat\": \"Débannir du chat\",\n        \"banFromChat\": \"Bannir du chat\",\n        \"banFromRoom\": \"Bannir de la salle\",\n        \"goBackToListener\": \"Retourner chez les auditeurs\",\n        \"deleteMessage\": \"Supprimer ce message\",\n        \"makeRoomCreator\": \"Transférer la propriété de la salle\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"requiert la permission de parler\",\n        \"makePublic\": \"rendre la salle publique\",\n        \"makePrivate\": \"rendre la salle privée\",\n        \"renamePublic\": \"Changer le nom de la salle publique\",\n        \"renamePrivate\": \"Changer le nom de la salle privée\",\n        \"chatDisabled\": \"Désactiver tchat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Votre sélection\" },\n    \"scheduledRooms\": {\n      \"title\": \"Salles programmées\",\n      \"noneFound\": \"aucune trouvée\",\n      \"allRooms\": \"toutes les salles programmées\",\n      \"myRooms\": \"mes salles programmées\",\n      \"scheduleRoomHeader\": \"Programmer une salle\",\n      \"startRoom\": \"lancer la salle\",\n      \"modal\": {\n        \"needsFuture\": \"doit dans être dans le futur\",\n        \"roomName\": \"nom de salle\",\n        \"roomDescription\": \"Description\",\n        \"minLength\": \"longueur minimum de 2 caractères\"\n      },\n      \"tommorow\": \"DEMAIN\",\n      \"today\": \"AUJOURD'HUI\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Êtes vous sûr de vouloir supprimer cette salle programmée?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Tchat\",\n      \"emotesSoon\": \"[emotes bientôt]\",\n      \"bannedAlert\": \"Vous avez été banni du tchat\",\n      \"waitAlert\": \"Vous devez attendre une seconde avant d'envoyer un nouveau message\",\n      \"search\": \"Rechercher\",\n      \"searchResults\": \"Résultats de recherche\",\n      \"recent\": \"Fréquemment utilisées\",\n      \"sendMessage\": \"Envoyer un message\",\n      \"whisper\": \"Chuchotement\",\n      \"welcomeMessage\": \"Bienvenue dans le tchat !\",\n      \"roomDescription\": \"Description de la salle\",\n      \"disabled\": \"Le tchat a été désactivé\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Ravitaillement de la fusée\",\n      \"takingOff\": \"Décollage\",\n      \"inSpace\": \"Dans l'espace\",\n      \"approachingMoon\": \"Approchant la lune\",\n      \"lunarDoge\": \"Doge Lunaire\",\n      \"approachingSun\": \"Approchant le soleil\",\n      \"solarDoge\": \"Doge Solaire\",\n      \"approachingGalaxy\": \"Approchant une galaxie\",\n      \"galacticDoge\": \"Doge Galactique\",\n      \"spottedLife\": \"Planète avec de la vie en vue\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/grc/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"φόρτωση περισσοτέρων\",\n    \"loading\": \"φόρτωση...\",\n    \"noUsersFound\": \"δεν βρέθηκαν χρήστες\",\n    \"ok\": \"οκ\",\n    \"yes\": \"ναι\",\n    \"no\": \"όχι\",\n    \"cancel\": \"ακύρωση\",\n    \"save\": \"αποθήκευση\",\n    \"edit\": \"επεξεργασία\",\n    \"delete\": \"διαγραφή\",\n    \"joinRoom\": \"είσοδος στο δωμάτιο\",\n    \"copyLink\": \"αντιγραφή συνδέσμου\",\n    \"copied\": \"αντιγράφτηκε\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Σε Σίγαση | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ιστορία Προέλευσης\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Αναφορά Προβλήματος\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"αποκλεισμός\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Χρήστες που ακολουθείτε και δεν βρίσκονται σε κάποιο δωμάτιο\",\n      \"currentRoom\": \"αυτή τη στιγμή βρίσκεστε σε:\",\n      \"startPrivateRoom\": \"δημιουργήστε ένα ιδιωτικό δωμάτιο μαζί τους\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Δημιουργία Δωματίου\",\n      \"refresh\": \"Επαναφόρτωση\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"το δωμάτιο διαγράφηκε, πηγαίνετε πίσω\",\n      \"shareRoomLink\": \"κοινοποιήστε τον σύνδεσμο του δωματίου\",\n      \"inviteFollowers\": \"Μπορείτε να προσκαλέσετε τους ακόλουθους σας που είναι ενεργοί:\",\n      \"whenFollowersOnline\": \"Όταν οι ακόλουθοι σας είναι ενεργοί, θα εμφανίζονται εδώ.\"\n    },\n    \"login\": {\n      \"headerText\": \"Πάμε τις φωνητικές συνομιλίες στο φεγγάρι 🚀\",\n      \"featureText_1\": \"Σκούρο Θέμα\",\n      \"featureText_2\": \"Ανοιχτές Εγγραφές\",\n      \"featureText_3\": \"Υποστήριξη Πολλαπλών Πλατφορμών\",\n      \"featureText_4\": \"Ανοιχτού Πηγαίου Κώδικα\",\n      \"featureText_5\": \"Συζήτηση Κειμένου\",\n      \"featureText_6\": \"Με την υποστήριξη του Doge\",\n      \"loginGithub\": \"σύνδεση μέσω GitHub\",\n      \"loginTwitter\": \"σύνδεση μέσω Twitter\",\n      \"createTestUser\": \"δημιουργήστε δοκιμαστικό χρήστη\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"αποσύνδεση\",\n      \"probablyLoading\": \"μάλλον φορτώνει...\",\n      \"voiceSettings\": \"πηγαίνετε στις ρυθμίσεις φωνής\",\n      \"soundSettings\": \"πηγαίνετε στις ρυθμίσεις ήχου\",\n      \"deleteAccount\": \"διαγραφή λογαριασμού\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ουπς! Αυτή η σελίδα χάθηκε στη συζήτηση.\",\n      \"goHomeMessage\": \"Μην ανησυχείτε. Μπορείτε να\",\n      \"goHomeLinkText\": \"επιστρέψετε στην αρχική\"\n    },\n    \"room\": {\n      \"speakers\": \"Ομιλητές\",\n      \"requestingToSpeak\": \"Αιτούμενοι να μιλήσουν\",\n      \"listeners\": \"Ακροατές\",\n      \"allowAll\": \"Επιτρέψτε τα όλα\",\n      \"allowAllConfirm\": \"Είστε σίγουρος; Αυτό θα επιτρέψει όλους τους {{count}} χρήστες που έχουν ζητήσει να μιλήσουν\"\n    },\n    \"searchUser\": { \"search\": \"αναζήτηση...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Ήχοι\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"επεξεργασία προφίλ\",\n      \"followsYou\": \"σας ακολουθεί\",\n      \"followers\": \"ακόλουθοι\",\n      \"following\": \"ακολουθείτε\",\n      \"followHim\": \"ακολουθήστε\",\n      \"followingHim\": \"ακολουθείτε\",\n      \"unfollow\": \"Unfollow\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ρυθμίσεις Φωνής\",\n      \"mic\": \"μικρόφωνο:\",\n      \"permissionError\": \"δεν βρέθηκε μικρόφωνο, είτε δεν έχετε κανένα συνδεδεμένο είτε δεν έχετε δώσει άδεια σε αυτή την ιστοσελίδα.\",\n      \"refresh\": \"ανανέωση λίστας μικροφώνων\",\n      \"volume\": \"ένταση ήχου:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Αποκλεισμένοι Χρήστες\",\n      \"unban\": \"ξε-αποκλεισμός\",\n      \"noBans\": \"κανένας δεν έχει αποκλειστεί ακόμα\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Αποχώρηση από το παρόν δωμάτιο\",\n      \"confirmLeaveRoom\": \"Είστε σίγουροι ότι θέλετε να αποχωρήσετε;\",\n      \"leave\": \"Αποχώρηση\",\n      \"inviteUsersToRoomBtn\": \"Προσκαλέστε χρήστες στο δωμάτιο\",\n      \"invite\": \"Πρόσκληση\",\n      \"toggleMuteMicBtn\": \"Εναλλαγή κατάστασης μικροφώνου\",\n      \"mute\": \"Σίγαση\",\n      \"unmute\": \"Ξε-σίγαση\",\n      \"makeRoomPublicBtn\": \"Κάντε το δωμάτιο δημόσιο!\",\n      \"settings\": \"Ρυθμίσεις\",\n      \"speaker\": \"Ομιλητής\",\n      \"listener\": \"Ακροατής\",\n      \"chat\": \"Συζήτηση\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Η συσκευή σας δεν υποστηρίζεται αυτή τη στιγμή. Μπορείτε να δημιουργήσετε ένα\",\n      \"linkText\": \"ζήτημα στο GitHub\",\n      \"addSupport\": \"και θα προσπαθήσουμε να προσθέσουμε υποστήριξη για την συσκευή σας.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"προσκλήθηκαν\",\n      \"inviteToRoom\": \"προσκαλέστε στο δωμάτιο\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Απορρίφθηκε η άδεια για πρόσβαση στο μικρόφωνό σας (ίσως χρειαστεί να πάτε στις ρυθμίσεις του περιηγητή σας και να κάνετε επαναφόρτωση της σελίδας)\",\n      \"dismiss\": \"παράλειψη\",\n      \"tryAgain\": \"προσπαθήστε ξανά\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"θέστε μια συντόμευση\",\n      \"listening\": \"ακροατές\",\n      \"toggleMuteKeybind\": \"συντόμευση εναλλαγής σίγασης\",\n      \"togglePushToTalkKeybind\": \"συντόμευση εναλλαγής push-to-talk\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"δεν υπάρχει δέκτης ήχου για κάποιο λόγο\"\n    },\n    \"addToCalendar\": { \"add\": \"Προσθήκη στο Ημερολόγιο\" },\n    \"wsKilled\": {\n      \"description\": \"Το WebSocket διακόπηκε από τον διακομιστή. Αυτό συμβαίνει συνήθως όταν ανοίγετε την ιστοσελίδα σε άλλη καρτέλα.\",\n      \"reconnect\": \"επανασύνδεση\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"δημόσιο\",\n        \"private\": \"ιδιωτικό\",\n        \"roomName\": \"όνομα δωματίου\",\n        \"roomDescription\": \"περιγραφή δωματίου\",\n        \"descriptionError\": \"μέγιστο μήκος 500\",\n        \"nameError\": \"πρέπει να έχει μήκος μεταξύ 2 και 60 χαρακτήρων\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Δημιουργήθηκε Νέο Δωμάτιο\",\n        \"roomInviteFrom\": \"Πρόσκληση σε Δωμάτιο από\",\n        \"justStarted\": \"Μόλις ξεκίνησαν\",\n        \"likeToJoin\": \", θα θέλατε να συμμετέχετε;\",\n        \"inviteReceived\": \"έχετε προσκληθεί στο\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"όνομα χρήστη μη διαθέσιμο\",\n        \"avatarUrlError\": \"Άκυρη φωτογραφία\",\n        \"avatarUrlLabel\": \"Διεύθυνση URL άβαταρ Github/Twitter/Discord\",\n        \"displayNameError\": \"μήκος από 2 έως 50 χαρακτήρες\",\n        \"displayNameLabel\": \"Εμφανιζόμενο Όνομα\",\n        \"usernameError\": \"μήκος από 4 έως 15 χαρακτήρες και μόνο αλφαριθμητικά/κάτω παύλα\",\n        \"usernameLabel\": \"Όνομα Χρήστη\",\n        \"bioError\": \"μέγιστο μήκος 160 χαρακτήρες\",\n        \"bioLabel\": \"Βιογραφικό\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Είστε σίγουροι ότι θέλετε να μπλοκάρετε αυτό το χρήστη από το να συμμετέχει σε οποιοδήποτε δωμάτιο δημιουργήσετε στο μέλλον;\",\n        \"blockUser\": \"μπλοκάρισμα χρήστη\",\n        \"makeMod\": \"προσθήκη ως συντονιστή\",\n        \"unmod\": \"αφαίρεση από συντονιστή\",\n        \"addAsSpeaker\": \"προσθήκη ως ομιλητή\",\n        \"moveToListener\": \"μετακίνηση σε ακροατή\",\n        \"banFromChat\": \"αποκλεισμός από τη συζήτηση\",\n        \"banFromRoom\": \"αποκλεισμός από το δωμάτιο\",\n        \"goBackToListener\": \"επιστροφή σε ακροατή\",\n        \"deleteMessage\": \"διαγράψτε αυτό το μήνυμα\",\n        \"makeRoomCreator\": \"μετατροπή σε διαχειριστή δωματίου\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"απαίτηση άδειας για ομιλία\",\n        \"makePublic\": \"κάντε το δωμάτιο δημόσιο\",\n        \"makePrivate\": \"κάντε το δωμάτιο ιδιωτικό\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Προγραμματισμένα Δωμάτια\",\n      \"noneFound\": \"δεν βρέθηκε κανένα\",\n      \"allRooms\": \"όλα τα προγραμματισμένα δωμάτια\",\n      \"myRooms\": \"τα προγραμματισμένα δωμάτια μου\",\n      \"scheduleRoomHeader\": \"Προγραμματισμός Δωματίου\",\n      \"startRoom\": \"έναρξη δωματίου\",\n      \"modal\": {\n        \"needsFuture\": \"πρέπει να είναι στο μέλλον\",\n        \"roomName\": \"όνομα δωματίου\",\n        \"roomDescription\": \"Περιγραφή Δωματίου\",\n        \"minLength\": \"ελάχιστο μήκος 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Συζήτηση\",\n      \"emotesSoon\": \"[τα emotes έρχονται σύντομα]\",\n      \"bannedAlert\": \"Αποκλειστήκατε από τη συζήτηση\",\n      \"waitAlert\": \"Πρέπει να περιμένετε ένα δευτερόλεπτο προτού στείλετε και άλλο μήνυμα\",\n      \"search\": \"Αναζήτηση\",\n      \"searchResults\": \"Αποτελέσματα Αναζήτησης\",\n      \"recent\": \"Χρησιμοποιούμενα Συχνά\",\n      \"sendMessage\": \"Στείλτε ένα Μήνυμα\",\n      \"whisper\": \"Ψιθυρίστε\",\n      \"welcomeMessage\": \"Καλως ήλθατε στη συζήτηση!\",\n      \"roomDescription\": \"Περιγραφή Δωματίου\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Ανεφοδιασμός πυραύλου\",\n      \"takingOff\": \"Απογείωση\",\n      \"inSpace\": \"Στο διάστημα\",\n      \"approachingMoon\": \"Πλησιάζοντας το φεγγάρι\",\n      \"lunarDoge\": \"Σεληνιακό Doge\",\n      \"approachingSun\": \"Πλησιάζοντας τον ήλιο\",\n      \"solarDoge\": \"Ηλιακό Doge\",\n      \"approachingGalaxy\": \"Πλησιάζουμε τον Γαλαξία\",\n      \"galacticDoge\": \"Γαλαξιακό Doge\",\n      \"spottedLife\": \"Εντοπίστηκε πλανήτης με ζωή\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/gsw/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Meh lade\",\n    \"loading\": \"Am lade...\",\n    \"noUsersFound\": \"Keni Benutzer gfunge\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ja\",\n    \"no\": \"Nei\",\n    \"cancel\": \"Abbreche\",\n    \"save\": \"Spichere\",\n    \"edit\": \"Bearbeite\",\n    \"delete\": \"Lösche\",\n    \"joinRoom\": \"Ruum bitrete\",\n    \"copyLink\": \"Link kopiere\",\n    \"copied\": \"kopiert\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Wenn du DogeHouse ohni Berechtigunge laufe lahsch chönnti es ungwollti Fehler gäh\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Stumm | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ursprungsgschicht\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Es Problem mälde\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"blockiere\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Benutzer, wo du fougsch, aber nid imne private Ruum si.\",\n      \"currentRoom\": \"Grad in:\",\n      \"startPrivateRoom\": \"En private Ruum mit däm Benutzer starte\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Folge\",\n      \"followingHim\": \"Am folge\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Ruum erstelle\",\n      \"refresh\": \"Aktualisiere\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Ruum isch nid gfunge worde! Zrugg\",\n      \"shareRoomLink\": \"Link zum Ruum teile\",\n      \"inviteFollowers\": \"Du chasch dini Abonnente, wo online si, ilade:\",\n      \"whenFollowersOnline\": \"Wenn dini Abonnente online si, werde sie da azeigt.\"\n    },\n    \"login\": {\n      \"headerText\": \"Mir bringe de Sprachchat zum Mond 🚀\",\n      \"featureText_1\": \"Dark-Mode\",\n      \"featureText_2\": \"Unbeschränkti Registrierig\",\n      \"featureText_3\": \"Plattformunabhängig\",\n      \"featureText_4\": \"Open-Source\",\n      \"featureText_5\": \"Text-Chat\",\n      \"featureText_6\": \"Atribe vo Doge\",\n      \"loginGithub\": \"mit GitHub amälde\",\n      \"loginTwitter\": \"mit Twitter amälde\",\n      \"createTestUser\": \"Testnutzer erstelle\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Abmälde\",\n      \"probablyLoading\": \"Ladt äuä...\",\n      \"voiceSettings\": \"Sprachistellige\",\n      \"soundSettings\": \"Tonistellige\",\n      \"deleteAccount\": \"Konto lösche\",\n      \"overlaySettings\": \"Gang zu de Overlay Istellige\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Hoppla! Die Site isch im Gspräch verlore gange.\",\n      \"goHomeMessage\": \"Mach dir kei Sorge. Du chasch\",\n      \"goHomeLinkText\": \"Hei gah\"\n    },\n    \"room\": {\n      \"speakers\": \"Sprächer\",\n      \"requestingToSpeak\": \"Gsprächsafrage\",\n      \"listeners\": \"Zuehörer\",\n      \"allowAll\": \"Alles erlaube\",\n      \"allowAllConfirm\": \"Bisch der sicher? Das wird all {{count}} Benutzer wo wennd rede dBerechtigung dazue gäh\"\n    },\n    \"searchUser\": { \"search\": \"Sueche...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Ton\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profil bearbeite\",\n      \"followsYou\": \"folgt dir\",\n      \"following\": \"Abonniert\",\n      \"followers\": \"Abonnente\",\n      \"followHim\": \"Folge\",\n      \"followingHim\": \"Am Folge\",\n      \"copyProfileUrl\": \"Profil URL kopiere\",\n      \"urlCopied\": \"URL id Zwüscheablag kopiert\",\n      \"unfollow\": \"Entfolge\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Sprachistellige\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Keis Mikrofon gfunge, es isch entweder keis agschlosse oder die Site het nid die nötige Berechtigunge.\",\n      \"volume\": \"Luutsterchi:\",\n      \"refresh\": \"Mikrofonliste aktualisiere\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Istellige\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"addToCalendar\": { \"add\": \"Zum Kalender hinzuefüege\" },\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"blockierti Benutzer\",\n      \"unban\": \"Blockierig ufhebe\",\n      \"noBans\": \"Du hesch aktuell niemer blockiert\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Aktuelle Ruum verlah\",\n      \"confirmLeaveRoom\": \"Bisch der sicher, dass du gah wettsch?\",\n      \"leave\": \"Verlah\",\n      \"inviteUsersToRoomBtn\": \"Benutzer zum Ruum ilade\",\n      \"invite\": \"Ilade\",\n      \"toggleMuteMicBtn\": \"Mikrofon ih-/usschalte\",\n      \"mute\": \"Stumm schalte\",\n      \"unmute\": \"Stummschaltig ufhebe\",\n      \"makeRoomPublicBtn\": \"Ruum öffentlech mache!\",\n      \"settings\": \"Istellige\",\n      \"speaker\": \"Sprächer\",\n      \"listener\": \"Zuehörer\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Mir hei ke Zuegriff uf dis Mikrofon. Mach dini Browseristellige uf, setz die nötige Berechtigunge und lad d Site neu.\",\n      \"dismiss\": \"verstange\",\n      \"tryAgain\": \"nomal versueche\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Öffentlech\",\n        \"private\": \"Privat\",\n        \"roomName\": \"Ruumname\",\n        \"roomDescription\": \"Ruumbeschribig\",\n        \"descriptionError\": \"die maximali Längi isch 500\",\n        \"nameError\": \"mues zwüsche 2 und 60 Zeiche lang sii\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Neue Ruum erstellt\",\n        \"roomInviteFrom\": \"Neui Iladig vo\",\n        \"justStarted\": \"Sie hend grad gstartet\",\n        \"likeToJoin\": \", wettsch biträte?\",\n        \"inviteReceived\": \"Du bisch iglade worde zu\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Benutzername isch scho vergäh\",\n        \"avatarUrlError\": \"ungültigs Bild\",\n        \"avatarUrlLabel\": \"GitHub / Twitter Avatar URL\",\n        \"displayNameError\": \"Längi: 2 bis 50 Buechstabe\",\n        \"displayNameLabel\": \"Azeigename\",\n        \"usernameError\": \"Längi: 4 bis 15 alphanumerischi Zeiche und Ungerstriche\",\n        \"usernameLabel\": \"Benutzername\",\n        \"bioError\": \"Maximal 160 Buechstabe\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Bisch der sicher, dass du dä Benutzer us aune dine zuekünftige Rüüm usschliesse wettsch?\",\n        \"blockUser\": \"Benutzer blockiere\",\n        \"makeMod\": \"Zum Moderator befördere\",\n        \"unmod\": \"Moderator Status ufhebe\",\n        \"addAsSpeaker\": \"zum Sprächer befördere\",\n        \"moveToListener\": \"zum Zuehörer abestufe\",\n        \"banFromChat\": \"Im Chat blockiere\",\n        \"banFromRoom\": \"Usem Ruum usschliesse\",\n        \"goBackToListener\": \"Zrügg zum Zuehörer\",\n        \"deleteMessage\": \"Nachricht lösche\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Berechtigung zum Rede verlange\",\n        \"makePublic\": \"Ruum öffentlech mache\",\n        \"makePrivate\": \"Ruum privat mache\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Us unbekannte Gründ isch ke Audio Consumer gfunge worde\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Tastenkürzu setze\",\n      \"listening\": \"Zuelose\",\n      \"toggleMuteKeybind\": \"Stummschalttaste umschalte\",\n      \"togglePushToTalkKeybind\": \"Push-to-Talk Tastekürzel umschalte\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"wsKilled\": {\n      \"description\": \"D Websocket Verbindig zum Server isch ungebroche worde. Das gscheht normalerwiis, wenn du d Website imne angere Tab öffnisch.\",\n      \"reconnect\": \"Nomau verbinge\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Dis Grät wird aktuell nid ungerstützt. Du chasch es\",\n      \"linkText\": \"Issue uf GitHub\",\n      \"addSupport\": \"erstelle und ich werde versueche, d Ungerstützig für dis Grät hinzuefüege.\"\n    },\n    \"inviteButton\": { \"invited\": \"iglade\", \"inviteToRoom\": \"zum Ruum ilade\" },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Planti Rüüm\",\n      \"noneFound\": \"Keni gfunge\",\n      \"allRooms\": \"Alli plante Rüüm\",\n      \"myRooms\": \"Mini plante Rüüm\",\n      \"scheduleRoomHeader\": \"E Ruum plane\",\n      \"startRoom\": \"Ruum starte\",\n      \"modal\": {\n        \"needsFuture\": \"Dr Zitpunkt mues ide Zuekunft lige\",\n        \"roomName\": \"Name vom Ruum\",\n        \"minLength\": \"minimali Längi: 2\",\n        \"roomDescription\": \"Beschribig\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[gli gits emotes]\",\n      \"bannedAlert\": \"Du bisch vom Chat usgschlosse\",\n      \"waitAlert\": \"Du muesch e Sekunde warte, bevor e witeri Nachricht schribe chasch\",\n      \"search\": \"Sueche\",\n      \"searchResults\": \"Suechergäbnis\",\n      \"recent\": \"Z'letscht gsuecht\",\n      \"sendMessage\": \"E Nachricht schicke\",\n      \"whisper\": \"Flüstere\",\n      \"welcomeMessage\": \"Willkomme im Chat!\",\n      \"roomDescription\": \"Ruumbeschribig\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Ragete wird tankt\",\n      \"takingOff\": \"D Ragete startet\",\n      \"inSpace\": \"Im Wäutruum\",\n      \"approachingMoon\": \"Geit ufe Mond zue\",\n      \"lunarDoge\": \"Mond-Doge\",\n      \"approachingSun\": \"Geit uf d Sunne zue\",\n      \"solarDoge\": \"Sunne-Doge\",\n      \"approachingGalaxy\": \"Geit uf d Galaxie zue\",\n      \"galacticDoge\": \"Galaxie-Doge\",\n      \"spottedLife\": \"Planet mit lebe gfunge\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/he/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"טען עוד\",\n    \"loading\": \"טוען...\",\n    \"noUsersFound\": \"לא נמצאו משתמשים\",\n    \"ok\": \"אוקי\",\n    \"yes\": \"כן\",\n    \"no\": \"לא\",\n    \"cancel\": \"בטל\",\n    \"save\": \"שמור\",\n    \"edit\": \"ערוך\",\n    \"delete\": \"מחק\",\n    \"joinRoom\": \"הצטרף לחדר\",\n    \"copyLink\": \"העתק קישור\",\n    \"copied\": \"הועתק\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"בלי ההרשאות יכולה לגרום לבעיות לא רצויות DogeHouse שים לב שהפעלת\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"מושתק | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"סיפור מקור\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"דווח על באג\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"באן\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"רשימת משתמשים שאתה עוקב אחרייהם שלא בחדר פרטי.\",\n      \"currentRoom\": \"ב:\",\n      \"startPrivateRoom\": \"צור חדר פרטי איתם\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"עקוב\",\n      \"followingHim\": \"עוקב\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"צור חדר\",\n      \"refresh\": \"טען מחדש\",\n      \"editRoom\": \"ערוך חדר\",\n      \"desktopAlert\": \"הורד את דוג' האוס דסקדופ היום!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"החדר נעלם, חזור\",\n      \"shareRoomLink\": \"שתף קישור לחדר שלך\",\n      \"inviteFollowers\": \"הזמן עוקבים:\",\n      \"whenFollowersOnline\": \" כשהעוקבים שלך יהיו מחוברים הם יופעו כאן.\"\n    },\n    \"login\": {\n      \"headerText\": \"לוקח שיחות קוליות לירח 🚀\",\n      \"featureText_1\": \"מצב חשוך\",\n      \"featureText_2\": \"פתח הרשמות\",\n      \"featureText_3\": \"תמיכה חוצה פלטפורמות\",\n      \"featureText_4\": \"קוד פתוח\",\n      \"featureText_5\": \"צ'אט\",\n      \"featureText_6\": \"מופעל על ידי דוג'\",\n      \"loginGithub\": \"התחבר באמצעות GitHub\",\n      \"loginTwitter\": \"התחבר באמצעות Twitter\",\n      \"createTestUser\": \"צור משתמש נסיון\",\n      \"loginDiscord\": \"התחבר עם דיסקורד\"\n    },\n    \"myProfile\": {\n      \"logout\": \"התנתק\",\n      \"probablyLoading\": \"כנראה טוען...\",\n      \"voiceSettings\": \"עבור להגדרות קול\",\n      \"soundSettings\": \"עבור להגדרות סאונד\",\n      \"deleteAccount\": \"מחק חשבון\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"סליחה, לא הצלחנו למצוא את משתמש זה.\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"אופס! הדף הזה הלך לאיבוד בשיחה.\",\n      \"goHomeMessage\": \"אל אל תדאג, אתה יכול.\",\n      \"goHomeLinkText\": \"חזור לדף הבית\"\n    },\n    \"room\": {\n      \"speakers\": \"מדברים\",\n      \"requestingToSpeak\": \"מבקשים לדבר\",\n      \"listeners\": \"מקשיבים\",\n      \"allowAll\": \"הרשה לכולם\",\n      \"allowAllConfirm\": \"המשתמשים המבקשים רשות יכולת דיבור {{count}} אתה בטוח? זה יאפשר לכל\"\n    },\n    \"searchUser\": { \"search\": \"חפש...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"צלילים\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"ערוך פרופיל\",\n      \"followsYou\": \"עוקב אחריך\",\n      \"followers\": \"עוקבים\",\n      \"following\": \"עוקב\",\n      \"followHim\": \"עקוב\",\n      \"followingHim\": \"עוקב\",\n      \"copyProfileUrl\": \"העתק קישור פרופיל\",\n      \"urlCopied\": \"הקישור הועתק\",\n      \"unfollow\": \"הפסק מעקב\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"הגדרות קול\",\n      \"mic\": \"מיקרופון:\",\n      \"permissionError\": \"לא נמצאו מיקרופונים, או שאין מיקרופון מחובר או שלא נתת אישור לאתר זה.\",\n      \"refresh\": \"רענן את רשימת המיקרופונים\",\n      \"volume\": \"עוצמת קול:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"מתחיל בהורדה...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"לחץ על כפתור זה כדי להתחיל בהורדה\",\n      \"download_now\": \"הורד עכשיו\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"משתמשים שקיבלו באן\",\n      \"unban\": \"הורד באן\",\n      \"noBans\": \"אף אחד לא קיבל באן\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"עזוב את החדר הנוכחי\",\n      \"confirmLeaveRoom\": \"בטוח שאתה רוצה לעזוב?\",\n      \"leave\": \"עזוב\",\n      \"inviteUsersToRoomBtn\": \"הזמן משתמשים לחדר\",\n      \"invite\": \"הזמנה\",\n      \"toggleMuteMicBtn\": \"השתק\",\n      \"mute\": \"השתק\",\n      \"unmute\": \"בטל השתקה\",\n      \"makeRoomPublicBtn\": \"הפוך את החדר לציבורי!\",\n      \"settings\": \"הגדרות\",\n      \"speaker\": \"דובר\",\n      \"listener\": \"מקשיב\",\n      \"chat\": \"צ'אט\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"המכשיר שלך אינו נתמך כרגע. אתה יכול ליצור\",\n      \"linkText\": \"בעיות בGitHub.\",\n      \"addSupport\": \"וננסה להוסיף תמיכה למכשיר שלך.\"\n    },\n    \"inviteButton\": { \"invited\": \"מוזמן\", \"inviteToRoom\": \"הזמן לחדר\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"ההרשאה נדחתה בניסיון לגשת למיקרופון שלך (יתכן שתצטרך להיכנס להגדרות הדפדפן ולטעון מחדש את הדף)\",\n      \"dismiss\": \"סגור\",\n      \"tryAgain\": \"נסה שוב\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"הגדר מקש\",\n      \"listening\": \"מקשיב\",\n      \"toggleMuteKeybind\": \"הפעל את מקש ההשתקה\",\n      \"togglePushToTalkKeybind\": \"הפעל push-to-talk\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"שום צרכן קול משום מה\" },\n    \"addToCalendar\": { \"add\": \"הוסף ליומן\" },\n    \"wsKilled\": {\n      \"description\": \"הWebSocket נסגר על ידי השרת. זה קורה בדרך כלל כשפותחים את האתר בלשונית אחרת.\",\n      \"reconnect\": \"התחבר מחדש\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"ציבורי\",\n        \"private\": \"פרטי\",\n        \"roomName\": \"שם החדר\",\n        \"roomDescription\": \"תיאור החדר\",\n        \"descriptionError\": \"אורך מקסימלי 500\",\n        \"nameError\": \"חייב להיות באורך של 2 עד 60 תווים\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"חדר חדש נוצר\",\n        \"roomInviteFrom\": \"הזמנת חדר מ\",\n        \"justStarted\": \"הם רק התחילו\",\n        \"likeToJoin\": \", תרצה להצטרף?\",\n        \"inviteReceived\": \"הוזמנת ל\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"שם המשתמש תפוס\",\n        \"avatarUrlError\": \"תמונה לא תקינה\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discordשל התמונה מ url\",\n        \"displayNameError\": \"אורך 2 עד 50 תווים\",\n        \"displayNameLabel\": \"שם מוצג\",\n        \"usernameError\": \"אורך 4 עד 15 תווים ורק אותיות / מספרים / קו תחתון\",\n        \"usernameLabel\": \"שם משתמש\",\n        \"bioError\": \"אורך מקסימלי של 160 תווים\",\n        \"bioLabel\": \"ביו\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"האם אתה בטוח שברצונך לחסום משתמש זה מלהצטרף לכל חדר שתיצור אי פעם?\",\n        \"blockUser\": \"חסום משתמש\",\n        \"makeMod\": \"הפוך למוד\",\n        \"unmod\": \"הסר מןד\",\n        \"addAsSpeaker\": \"הוסף כ דובר\",\n        \"moveToListener\": \"העבר למקשיב\",\n        \"banFromChat\": \"הרחק מהצ'אט\",\n        \"banFromRoom\": \"הרחק מהחדר\",\n        \"goBackToListener\": \"חזור למקשיב\",\n        \"deleteMessage\": \"מחק הודעה זו\",\n        \"makeRoomCreator\": \"הפוך למנהל חדר\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"דרוש אישור לדבר\",\n        \"makePublic\": \"הפוך חדר לציבורי\",\n        \"makePrivate\": \"הפוך חדר לפרטי\",\n        \"renamePublic\": \"הגדר את שם החדר הציבורי\",\n        \"renamePrivate\": \"הגדר את השם של החדר הפרטי\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"מחובר\",\n      \"noOnline\": \"יש לך 0 חברים מחוברים כרגע\",\n      \"showMore\": \"הראה עוד\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"טען עוד חדרים\"\n    },\n    \"search\": {\n      \"placeholder\": \"חפש חדרים, משתמשים, או קטגוריות\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"פרופיל\",\n      \"language\": \"שפה\",\n      \"reportABug\": \"דווח על באג\",\n      \"useOldVersion\": \"השתמש בגרסא ישנה\",\n      \"logOut\": {\n        \"button\": \"יציאה מהמשתמש\",\n        \"modalSubtitle\": \"האם אתה בטוח שתרצה לצאת מהמשתמש ?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"חדרים מתוזמנים\",\n      \"noneFound\": \"לא נמצאו\",\n      \"allRooms\": \"כל החדרים המתוזמנים\",\n      \"myRooms\": \"החדרים המתוזמנים שלי\",\n      \"scheduleRoomHeader\": \"צור חדר מתוזמן\",\n      \"startRoom\": \"התחל חדר\",\n      \"modal\": {\n        \"needsFuture\": \"חייב להיות בעתיד\",\n        \"roomName\": \"שם החדר\",\n        \"minLength\": \"אורך מינימלי 2\",\n        \"roomDescription\": \"תיאור החדר\"\n      },\n      \"tommorow\": \"מחר\",\n      \"today\": \"היום\",\n      \"deleteModal\": { \"areYouSure\": \"האם אתה בטוח שתרצה למחוק את החדר הזה?\" }\n    },\n    \"roomChat\": {\n      \"title\": \"צ'אט\",\n      \"emotesSoon\": \"[אימוג'ים בקרוב]\",\n      \"bannedAlert\": \"קיבלת באן מהצ'אט\",\n      \"waitAlert\": \"חכה שנייה לפני שליחת הודעה\",\n      \"search\": \"חפש\",\n      \"searchResults\": \"תוצאות חיפוש\",\n      \"recent\": \"לעיתים קרובות בשימוש\",\n      \"sendMessage\": \"שלח הודעה\",\n      \"whisper\": \"הודעה פרטית\",\n      \"welcomeMessage\": \"ברוכים הבאים לצ'אט!\",\n      \"roomDescription\": \"תיאור החדר\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"מטדלק את הרקטה\",\n      \"takingOff\": \"ממריא\",\n      \"inSpace\": \"בחלל\",\n      \"approachingMoon\": \"מתקרב לירח\",\n      \"lunarDoge\": \"ירח doge\",\n      \"approachingSun\": \"מתקרב לשמש\",\n      \"solarDoge\": \"שמש doge\",\n      \"approachingGalaxy\": \"מתקרב לגלקסיה\",\n      \"galacticDoge\": \"גלקטי Doge\",\n      \"spottedLife\": \"כוכב לכת עם חיים נמצא\"\n    },\n    \"feed\": { \"yourFeed\": \"הסיפור שלך\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/hi/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"और लोड करें\",\n    \"loading\": \"लोड हो रहा है...\",\n    \"noUsersFound\": \"कोई उपयोग्कर्ता नहीं मिले\",\n    \"ok\": \"ठीक है\",\n    \"yes\": \"सही\",\n    \"no\": \"गलत\",\n    \"cancel\": \"रद्द करें\",\n    \"save\": \"सेव/बचा ले\",\n    \"edit\": \"संपादित करे\",\n    \"delete\": \"हटाओ\",\n    \"joinRoom\": \"कमरे में शामिल हों\",\n    \"copyLink\": \"कॉपी\",\n    \"copied\": \"कॉपी हो गया\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"कृपया ध्यान दें कि एक्सेसिबिलिटी परमिशन के बिना दोजेहाउस चलाना अवांछित त्रुटियों का कारण हो सकता है\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"शूरूआत की कहानी\",\n    \"link_2\": \"डिस्कॉर्ड \",\n    \"link_3\": \"रिपोर्ट अ बग \"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"उन उपयोगकर्ताओं की सूची जो एक प्राइवेट रूम में नहीं हैं और आप उन्हें फॉलो करते हैं।\",\n      \"currentRoom\": \"वर्तमान में:\",\n      \"startPrivateRoom\": \"उनके साथ एक प्राइवेट रूम शुरू करें\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"रूम बनाये\",\n      \"refresh\": \"रिफ्रेश\",\n      \"editRoom\": \"एडिट रूम \",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"रूम गया, वापस जाए\",\n      \"shareRoomLink\": \"रूम की लिंक शेयर करें\",\n      \"inviteFollowers\": \"आप अपने ऑनलाइन फॉलोअर्स को आमंत्रित भेज सकते हैं:\",\n      \"whenFollowersOnline\": \"जब आपके फॉलोअर्स ऑनलाइन होंगे, वे यहां दिखेंगे।\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"log in with GitHub\",\n      \"loginTwitter\": \"log in with Twitter\",\n      \"createTestUser\": \"create test user\",\n      \"loginDiscord\": \"Login with Discord  \"\n    },\n    \"myProfile\": {\n      \"logout\": \"logout\",\n      \"probablyLoading\": \"शायद लोड हो रहा है...\",\n      \"voiceSettings\": \"वॉइस सेटिंग्स में जाएं\",\n      \"soundSettings\": \"साउंड सेटिंग में जाएं\",\n      \"deleteAccount\": \"खाता हटाएं\",\n      \"overlaySettings\": \"ओवरले सेटिंग्स पर जाएं\",\n      \"couldNotFindUser\": \"क्षमा करें, हमें वह उपयोगकर्ता नहीं मिला\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"वूप्स! यह पेज बातचीत में खो गया.\",\n      \"goHomeMessage\": \"चिंता नहीं करे। आप ऐसा कर सकते हैं।\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"room\": {\n      \"speakers\": \"बोलनेवाले\",\n      \"requestingToSpeak\": \"बोलने की अनुमति माँग रहे है\",\n      \"listeners\": \"सुननेवाले\",\n      \"allowAll\": \"अल्लोव आल \",\n      \"allowAllConfirm\": \"अरे  यू  सूरे ? थिस  विल अल्लोव आल {{count}} रिक्वेस्टिंग  उसेर्स  तो  स्पीक \"\n    },\n    \"searchUser\": { \"search\": \"खोज करे...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"आवाज़\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"edit profile\",\n      \"followsYou\": \"follows you\",\n      \"followers\": \"फॉलोअर्स\",\n      \"following\": \"फॉलोइंग\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"आवाज़ की सेटिंग्स\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"कोई माइक नहीं मिला, आपने या तो इस वेबसाइट को अनुमति नहीं दी है या माइक प्लग इन नहीं किया है।\",\n      \"refresh\": \"रिफ्रेश माइक लिस्ट\",\n      \"volume\": \"volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Banned Users\",\n      \"unban\": \"unban\",\n      \"noBans\": \"no one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"वर्तमान रूम छोड़े\",\n      \"confirmLeaveRoom\": \"क्या आप छोड़ना चाहते हैं?\",\n      \"leave\": \"लीव\",\n      \"inviteUsersToRoomBtn\": \"उपयोगकर्ताओं को रूम में आमंत्रित करें\",\n      \"invite\": \"आमंत्रण\",\n      \"toggleMuteMicBtn\": \"म्यूट माइक्रोफोन टॉगल करें\",\n      \"mute\": \"म्यूट\",\n      \"unmute\": \"अनम्यूट\",\n      \"makeRoomPublicBtn\": \"रूम को सार्वजनिक करें!\",\n      \"settings\": \"सेटिंग्स\",\n      \"speaker\": \"बोलनेवाले\",\n      \"listener\": \"सुननेवाले\",\n      \"chat\": \"चैट\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"वर्तमान में आपका डिवाइस समर्थित नहीं है। आप एक\",\n      \"linkText\": \"GitHub issue\",\n      \"addSupport\": \"बना सकते है|  हम आपके डिवाइस के लिए समर्थन जोड़ने का प्रयास करेंगे।\"\n    },\n    \"inviteButton\": { \"invited\": \"invited\", \"inviteToRoom\": \"invite to room\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"आपने माइक को उपयोग करने की अनुमति को इनकार कर दी है (आपको ब्राउज़र सेटिंग्स में जा के सैट करें या वेबसाइट को फिर से लोड करने की आवश्यकता हो सकती है)\",\n      \"dismiss\": \"डिसमिस\",\n      \"tryAgain\": \"फिर प्रयास करें\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"शॉर्टकट सैट करें\",\n      \"listening\": \"सुन रहे है\",\n      \"toggleMuteKeybind\": \"टॉगल म्यूट कीबाइंड\",\n      \"togglePushToTalkKeybind\": \"टॉगल push-to-talk कीबाइंड\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"किसी कारण से कोई ऑडियो उपभोक्ता नहीं है\"\n    },\n    \"addToCalendar\": { \"add\": \"कैलेंडर में जोड़ें\" },\n    \"wsKilled\": {\n      \"description\": \"Websocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"फिर कनेक्ट करे\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"सार्वजनिक\",\n        \"private\": \"प्राइवेट\",\n        \"roomName\": \"रूम का नाम\",\n        \"roomDescription\": \"रूम के बारे में\",\n        \"descriptionError\": \"500 अक्षर से कम \",\n        \"nameError\": \"2 अक्षर से जयादा और 60 अक्षर से कम के बीच में होने चाहए\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"नया रूम बन गया है\",\n        \"roomInviteFrom\": \"रूम का निमंत्रण द्वारा\",\n        \"justStarted\": \"उन्होंने अभी शुरुआत की है\",\n        \"likeToJoin\": \", क्या आप शामिल होना चाहेंगे?\",\n        \"inviteReceived\": \"आपको आमंत्रित किया गया है\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"length 2 to 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"max length of 160 characters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"क्या आप इस उपयोगकर्ता को आपके द्वारा बनाए गए सभी रूम में शामिल होने से रोकना चाहते हैं?\",\n        \"blockUser\": \"block user\",\n        \"makeMod\": \"make mod\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"स्पीकर के रूप में जोड़ें\",\n        \"moveToListener\": \"move to listener\",\n        \"banFromChat\": \"ban from chat\",\n        \"banFromRoom\": \"ban from room\",\n        \"goBackToListener\": \"go back to listener\",\n        \"deleteMessage\": \"इस मैसेज को हटा दें\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"बोलने की अनुमति की आवश्यकता है\",\n        \"makePublic\": \"रूम को सार्वजनिक करें\",\n        \"makePrivate\": \"रूम को प्राइवेट करें!\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"अनुसूचित रूम\",\n      \"noneFound\": \"कोई नहीं मिला\",\n      \"allRooms\": \"सभी अनुसूचित रूम\",\n      \"myRooms\": \"मेरे अनुसूचित रूम\",\n      \"scheduleRoomHeader\": \"अनुसूचित रूम\",\n      \"startRoom\": \"नया रूम\",\n      \"modal\": {\n        \"needsFuture\": \"भविष्य में होना चाहिए\",\n        \"roomName\": \"रूम का नाम\",\n        \"minLength\": \"काम से काम 2\",\n        \"roomDescription\": \"रूम के बारे में\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"चैट\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"आपको चैट से प्रतिबंधित कर दिया गया है\",\n      \"waitAlert\": \"आपको दूसरा मैसेज भेजने से पहले एक सेकंड इंतजार करना होगा\",\n      \"search\": \"खोज करे\",\n      \"searchResults\": \"खोज के परिणाम\",\n      \"recent\": \"ज्यदातर इस्तेमाल किया जाने वाला\",\n      \"sendMessage\": \"एक मैसेज भेजो\",\n      \"whisper\": \"व्हिस्पर \",\n      \"welcomeMessage\": \"चैट में आपका स्वागत है!\",\n      \"roomDescription\": \"रूम के बारे में\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"अलविदा\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/hr/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"učitaj više\",\n    \"loading\": \"učitavanje ...\",\n    \"noUsersFound\": \"nije pronađen nijedan korisnik\",\n    \"ok\": \"ok\",\n    \"yes\": \"da\",\n    \"no\": \"ne\",\n    \"cancel\": \"Otkaži\",\n    \"save\": \"spremi\",\n    \"edit\": \"uredi\",\n    \"delete\": \"brisanje\",\n    \"joinRoom\": \"pridruži se sobi\",\n    \"copyLink\": \"kopiraj vezu\",\n    \"copied\": \"kopirano\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Molimo Vas da upamtite da bi korištenje DogeHouse-a bez dozvola za pristupačnost moglo uzrokovati neke neželjene greške\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Prigušen | DogeHouse\",\n    \"deafenedTitle\": \"Oglušen | DogeHouse\",\n    \"dashboard\": \"Nadzorna ploča\",\n    \"connectionTaken\": \"Veza Zauzeta\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Priča o nastanku\",\n    \"link_2\": \"Nesklad\",\n    \"link_3\": \"Prijavi grešku\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization String\",\n    \"admin\": {\n      \"ban\": \"zabrani\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Popis korisnika koji nisu u privatnoj sobi, a vi ih pratite\",\n      \"currentRoom\": \"trenutno u:\",\n      \"startPrivateRoom\": \"započnite privatnu sobu s njima\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"zaprati\",\n      \"followingHim\": \"pratitelji\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Stvori sobu\",\n      \"refresh\": \"Osvježi\",\n      \"editRoom\": \"Uredi Sobu\",\n      \"desktopAlert\": \"Preuzmite DogeHouse Desktop aplikaciju danas!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"soba je nestala, idi natrag\",\n      \"shareRoomLink\": \"podijeli vezu do sobe\",\n      \"inviteFollowers\": \"Možete pozvati svoje pratitelje koji su na mreži:\",\n      \"whenFollowersOnline\": \"Kad su vaši pratitelji na mreži, oni će se pojaviti ovdje.\"\n    },\n    \"login\": {\n      \"headerText\": \"Dovodimo glasovne razgovore na mjesec 🚀\",\n      \"featureText_1\": \"Tamna tema\",\n      \"featureText_2\": \"Otvorene prijave\",\n      \"featureText_3\": \"Podrška za više platformi\",\n      \"featureText_4\": \"Otvoreni kod\",\n      \"featureText_5\": \"Tekstualni chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"prijava GitHub-om\",\n      \"loginTwitter\": \"prijava Twitter-om\",\n      \"createTestUser\": \"izradi test korisnika\",\n      \"loginDiscord\": \"Prijava Discord-om\"\n    },\n    \"myProfile\": {\n      \"logout\": \"odjava\",\n      \"probablyLoading\": \"vjerojatno se učitava ...\",\n      \"voiceSettings\": \"idite na glasovne postavke\",\n      \"soundSettings\": \"idite na postavke zvuka\",\n      \"deleteAccount\": \"izbriši račun\",\n      \"overlaySettings\": \"idi na postavke prekrivača\",\n      \"couldNotFindUser\": \"Oprostite, nismo mogli pronači tog korisnika\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Ova se stranica izgubila u razgovoru.\",\n      \"goHomeMessage\": \"Ne brinite. Možete\",\n      \"goHomeLinkText\": \"idi kući\"\n    },\n    \"room\": {\n      \"speaker\": \"Govornici\",\n      \"requestingToSpeak\": \"Zahtjev za govor\",\n      \"listeners\": \"Slušatelji\",\n      \"allowAll\": \"Dopusti sve\",\n      \"allowAllConfirm\": \"Jeste li sigurni? Ovo će omogućiti svim {{count}} korisnicima koji zahtijevaju da govore\",\n      \"speakers\": \"Govornici\"\n    },\n    \"searchUser\": { \"search\": \"traži...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Zvuči\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"uredi profil\",\n      \"followYou\": \"vas prati\",\n      \"followers\": \"pratitelja\",\n      \"following\": \"pratite\",\n      \"followHim\": \"zapratite\",\n      \"followingHim\": \"pratite\",\n      \"followsYou\": \"vas prati\",\n      \"copyProfileUrl\": \"kopiraj url profila\",\n      \"urlCopied\": \"URL kopiran u međuspremnik\",\n      \"unfollow\": \"Prestani pratiti\",\n      \"about\": \"O\",\n      \"bot\": \"Robot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Postavke glasa\",\n      \"mic\": \"mikrofon:\",\n      \"permissionError\": \"nisu pronađeni mikrofoni, ili ih nitko nije priključio ili niste dali dopuštenje ovom web mjestu.\",\n      \"refresh\": \"osvježi popis mikrofona\",\n      \"volume\": \"jačina zvuka:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Postavke Prekrivača\",\n      \"input\": {\n        \"errorMsg\": \"Molimo vas da unesete važeći naslov aplikacije\",\n        \"label\": \"Upišite naslov aplikacije\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Započinjemo preuzimanje...\",\n      \"failed\": \"Nismo mogli automatski preuzeti, molimo Vas pokušajte kasnije\",\n      \"visit_gh\": \"Posjetite GitHub izdanja\",\n      \"prompt\": \"Pritisnite na gumb kako biste započeli preuzimanje\",\n      \"download_now\": \"Preuzmi Sada\",\n      \"download_for\": \"Preuzmi za {{platform}}\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Zabranjeni korisnici\",\n      \"unban\": \"makni zabranu\",\n      \"noBans\": \"još nitko nije zabranjen\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Napusti trenutnu sobu\",\n      \"confirmLeaveRoom\": \"Jeste li sigurni da želite otići?\",\n      \"leave\": \"Napusti\",\n      \"inviteUsersToRoomBtn\": \"Pozovite korisnike u sobu\",\n      \"invite\": \"Pozovite\",\n      \"toggleMuteMicBtn\": \"Uključivanje / isključivanje mikrofona\",\n      \"mute\": \"Isključi zvuk\",\n      \"unmute\": \"Uključi zvuk\",\n      \"makeRoomPublicBtn\": \"Učini sobu javnom!\",\n      \"settings\": \"Postavke\",\n      \"speaker\": \"Govornik\",\n      \"listener\": \"Slušatelj\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Uključivanje / isključivanje Oglušenja\",\n      \"deafen\": \"Ogluši\",\n      \"undeafen\": \"Ozvuči\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Vaš uređaj trenutno nije podržan. Možete stvoriti\",\n      \"linkText\": \"'issue' na GitHubu\",\n      \"addSupport\": \"i pokušati ćemo dodati podršku za vaš uređaj.\"\n    },\n    \"inviteButton\": { \"invited\": \"pozvan\", \"inviteToRoom\": \"pozovi u sobu\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Dozvola odbijena za pokušaj pristupa vašem mikrofonu (možda ćete trebati ući u postavke preglednika i ponovo učitati stranicu)\",\n      \"dismiss\": \"zanemari\",\n      \"tryAgain\": \"pokušaj ponovo\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"postavi tipkovnički prečac\",\n      \"listening\": \"Slušanje\",\n      \"toggleMuteKeybind\": \"uključivanje/isključivanje isključivanja tipkovničkih prečaca\",\n      \"togglePushToTalkKeybind\": \"uključivanje/isključivanje push-to-talk\",\n      \"toggleOverlayKeybind\": \"Prećac za uključivanje / isključivanje prekrivača\",\n      \"toggleDeafKeybind\": \"Prećac za uključivanje / isključivanje oglušenja\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"iz nekog razloga nema potrošača zvuka\"\n    },\n    \"addToCalendar\": { \"add\": \"Dodaj u kalendar\" },\n    \"wsKilled\": {\n      \"description\": \"Poslužitelj je ubio WebSocket. To se obično događa kada web mjesto otvorite na drugoj kartici.\",\n      \"reconnect\": \"ponovno se poveži\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Javno\",\n        \"private\": \"Privatno\",\n        \"roomName\": \"Naziv sobe\",\n        \"roomDescription\": \"Opis sobe\",\n        \"descriptionError\": \"maksimalna duljina 500\",\n        \"nameError\": \"mora imati između 2 i 60 znakova\",\n        \"subtitle\": \"Unesite sljedeća polja kako bi ste stvorili novu sobu\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nova soba stvorena\",\n        \"roomInviteFrom\": \"Poziv u sobu od\",\n        \"justStarted\": \"Tek su započeli\",\n        \"likeToJoin\": \", želite li se pridružiti?\",\n        \"inviteReceived\": \"pozvani ste u\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"korisničko ime zauzeto\",\n        \"avatarUrlError\": \"Neispravna slika\",\n        \"avatarUrlLabel\": \"GitHub / Twitter avatar url\",\n        \"displayNameError\": \"duljina od 2 do 50 znakova\",\n        \"displayNameLabel\": \"Ime za prikaz\",\n        \"usernameError\": \"duljina od 4 do 15 znakova i samo alfanumerički / donji znak\",\n        \"usernameLabel\": \"Korisničko ime\",\n        \"bioError\": \"maksimalna duljina od 160 znakova\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Jeste li sigurni da želite blokirati ovog korisnika od toga da se pridruži bilo kojoj sobi koju ste ikad stvorili?\",\n        \"blockUser\": \"blokiraj korisnika\",\n        \"makeMod\": \"postavi za moderatora\",\n        \"unmod\": \"makni moderatora\",\n        \"addAsSpeaker\": \"dodaj kao govnornika\",\n        \"moveToListener\": \"prebaci u slušatelja\",\n        \"banFromChat\": \"zabrani mu chat\",\n        \"banFromRoom\": \"zabrani mu pristup sobi\",\n        \"goBackToListener\": \"vratite se na slušatelj\",\n        \"deleteMessage\": \"izbriši ovu poruku\",\n        \"makeRoomCreator\": \"napravi administratorom sobe\",\n        \"unBanFromChat\": \"Makni zabranu sa Chat-a\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"zahtijeva dopuštenje za govor\",\n        \"makePublic\": \"učini sobu javnom\",\n        \"makePrivate\": \"učini sobu privatnom\",\n        \"renamePublic\": \"Postavi ime javne sobe\",\n        \"renamePrivate\": \"Postavi ime privatne sobe\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Ljudi\",\n      \"online\": \"NA MREŽI\",\n      \"noOnline\": \"Imate 0 prijatelja koji su trenutno na mreži\",\n      \"showMore\": \"Prikaži još\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Nadolazeće sobe\",\n      \"exploreMoreRooms\": \"Istraži još soba\"\n    },\n    \"search\": {\n      \"placeholder\": \"Potraži za sobe, korisnike ili kategorije\",\n      \"placeholderShort\": \"Potraži\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Jezik\",\n      \"reportABug\": \"Prijavi Bug\",\n      \"useOldVersion\": \"Koristi Staru verziju\",\n      \"logOut\": {\n        \"button\": \"Odjava\",\n        \"modalSubtitle\": \"Jeste li sigurni da se želite odjaviti?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debuggaj Audio\",\n        \"stopDebugger\": \"Zaustavi Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Planirane sobe\",\n      \"noneFound\": \"nije pronađen\",\n      \"allRooms\": \"sve predviđene sobe\",\n      \"myRooms\": \"moje predviđene sobe\",\n      \"scheduleRoomHeader\": \"Raspored prostorija\",\n      \"startRoom\": \"start soba\",\n      \"modal\": {\n        \"needsFuture\": \"mora biti u budućnosti\",\n        \"roomName\": \"ime sobe\",\n        \"roomDescription\": \"Opis\",\n        \"minLength\": \"min duljina 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Jeste li sigurni da želite izbrisati ovu nadolazeću sobu?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotikoni uskoro]\",\n      \"bannedAlert\": \"Dobili ste zabranu chatanja\",\n      \"waitAlert\": \"Prije slanja nove poruke morate pričekati sekundu\",\n      \"search\": \"Traži\",\n      \"searchResults\": \"Rezultati pretraživanja\",\n      \"recent\": \"Često se koristi\",\n      \"sendMessage\": \"Pošalji poruku\",\n      \"whisper\": \"Šaputanje\",\n      \"welcomeMessage\": \"Dobrodošli u chat!\",\n      \"roomDescription\": \"opis sobe\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Raketa za punjenje gorivom\",\n      \"takingOff\": \"Polijetanje\",\n      \"inSpace\": \"U svemiru\",\n      \"approachingMoon\": \"Približavanje Mjeseca\",\n      \"lunarDoge\": \"Lunarni doge\",\n      \"approachingSun\": \"Približavanje suncu\",\n      \"solarDoge\": \"Solarni doge\",\n      \"approachingGalaxy\": \"Približavanje galaksiji\",\n      \"galacticDoge\": \"Galaktički doge\",\n      \"spottedLife\": \"Planeta sa životom uočena\"\n    },\n    \"feed\": { \"yourFeed\": \"Vaš Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/hu/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Továbbiak betöltése\",\n    \"loading\": \"Töltés...\",\n    \"noUsersFound\": \"Nem található felhasználó!\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Igen\",\n    \"no\": \"Nem\",\n    \"cancel\": \"Mégse\",\n    \"save\": \"Mentés\",\n    \"edit\": \"Szerkesztés\",\n    \"delete\": \"Törlés\",\n    \"joinRoom\": \"Csatlakozás\",\n    \"copyLink\": \"Link másolása\",\n    \"copied\": \"Kimásolva!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Figyelem, a DogeHouse kisegítő lehetőségek nélküli futtatása nem kívánt hibákat okozhat\",\n    \"copy\": \"Másolás\",\n    \"error\": \"Hiba\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Némítva | DogeHouse\",\n    \"deafenedTitle\": \"Süketítve | DogeHouse\",\n    \"dashboard\": \"Home\",\n    \"connectionTaken\": \"Kapcsolat megszakítva\"\n  },\n  \"footer\": {\n    \"link_1\": \"Története\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Hiba jelentése\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban\",\n      \"userStaffandContrib\": \"Staff & Hozzájáruló felhasználók\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"hozzájárulás\",\n      \"username\": \"Felhasználónév\",\n      \"usrStaff\": \"Staff felhasználó\",\n      \"usrContributions\": \"DogeHouse hozzájáruló\",\n      \"reason\": \"oka\",\n      \"usernamePlaceholder\": \"felhasználónév a műveletek végrehajtásához\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Követett felhasználók nyilvános szobában.\",\n      \"currentRoom\": \"Jelenleg itt:\",\n      \"startPrivateRoom\": \"Privát szoba létrehozása velük\",\n      \"title\": \"Felhasználok\"\n    },\n    \"followList\": {\n      \"followHim\": \"Követés\",\n      \"followingHim\": \"Követve\",\n      \"title\": \"Felhasználók\",\n      \"followingNone\": \"Nem követ senkit\",\n      \"noFollowers\": \"Nincsenek követők\"\n    },\n    \"home\": {\n      \"createRoom\": \"Szoba létrehozása\",\n      \"refresh\": \"Frissítés\",\n      \"editRoom\": \"Szoba szerkesztése\",\n      \"desktopAlert\": \"Töltse le a DogeHouse asztali alkalmazást még ma!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"A beszélgetés véget ért\",\n      \"shareRoomLink\": \"Szoba linkjének megosztása\",\n      \"inviteFollowers\": \"Meghívhatja jelenleg online követőit:\",\n      \"whenFollowersOnline\": \"Online követőid itt fognak megjelenni.\"\n    },\n    \"login\": {\n      \"headerText\": \" Hangbeszélgetéseket a Holdra 🚀\",\n      \"featureText_1\": \"Sötét Téma\",\n      \"featureText_2\": \"Nyitott regisztrációk\",\n      \"featureText_3\": \"Platformok közötti támogatás\",\n      \"featureText_4\": \"Nyílt forráskód\",\n      \"featureText_5\": \"Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Bejelentkezés GitHub fiókkal\",\n      \"loginTwitter\": \"Bejelentkezés Twitter fiókkal\",\n      \"createTestUser\": \"Teszt felhasználó létrehozása\",\n      \"loginDiscord\": \"Bejelentkezés Discord fiókkal\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Kijelentkezés\",\n      \"probablyLoading\": \"Valószínüleg tölt...\",\n      \"voiceSettings\": \"Hangbeállítások\",\n      \"soundSettings\": \"Hanghatások\",\n      \"deleteAccount\": \"Fiók törlése\",\n      \"overlaySettings\": \"Átfedés beállítások megnyitása\",\n      \"couldNotFindUser\": \"Sajnáljuk, a felhasználó nem található\",\n      \"privacySettings\": \"Személyes tér beállításai\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! Ez az oldal elveszett a beszélgetésben.\",\n      \"goHomeMessage\": \"Ne aggódjon. Vissza tud lépni a\",\n      \"goHomeLinkText\": \"főoldalra\"\n    },\n    \"room\": {\n      \"speakers\": \"Beszélők\",\n      \"requestingToSpeak\": \"Beszélő jog kérése\",\n      \"listeners\": \"Hallgatók\",\n      \"allowAll\": \"Mindegyik engedélyezése\",\n      \"allowAllConfirm\": \"Biztos benne? Ezzel engedélyezi mind a {{count}} felhasználónak, hogy beszéljen\"\n    },\n    \"searchUser\": { \"search\": \"keresés...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Hangok\",\n      \"title\": \"Hangbeállítások\",\n      \"playSound\": \"Hang lejátszása\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Fiók szerkesztése\",\n      \"followsYou\": \"Követ téged\",\n      \"followers\": \"követő\",\n      \"following\": \"követés\",\n      \"followHim\": \"Követés\",\n      \"followingHim\": \"Követve\",\n      \"copyProfileUrl\": \"Fiók URL címének másolása\",\n      \"urlCopied\": \"URL cím kimásolva a vágólapra\",\n      \"unfollow\": \"Követés megszűntetése\",\n      \"about\": \"\",\n      \"aboutSuffix\": \"névjegye\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Névjegy\",\n        \"rooms\": \"Szobák\",\n        \"scheduled\": \"Ütemzett\",\n        \"recorded\": \"Felvett\",\n        \"clips\": \"Klippek\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Leiltás\",\n      \"unblock\": \"Tiltás feloldása\",\n      \"sendDM\": \"Üzenet küldése\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Hangbeállítások\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Nem található mikrofon. Vagy nincs csatlakoztatva, vagy nem adta meg az engedélyt.\",\n      \"refresh\": \"Elérhető mikrofonok frissítése\",\n      \"volume\": \"Hangerő:\",\n      \"title\": \"Hangbeállítások\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Átfedés beállítások\"\n    },\n    \"download\": {\n      \"starting\": \"Letöltés elkezdve...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"GitHub kiadások megtekintése\",\n      \"prompt\": \"A gombra kattintva elkezdheti a letöltést\",\n      \"download_now\": \"Letöltés\",\n      \"download_for\": \"Letöltés %platform%-ra (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Személyes tér beállításai\",\n      \"header\": \"Személyes tér\",\n      \"whispers\": { \"label\": \"Suttogó üzenetek\", \"on\": \"Bekapcsolva\", \"off\": \"Kikapcsolva\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Bot fiókjaid\",\n      \"bots\": \"Botok\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"Api kulcs\",\n      \"regenerate\": \"Újragenerálás\",\n      \"reveal\": \"Kattintson az api kulcs megjelenítéséhez\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Kitiltott felhasználók\",\n      \"unban\": \"Kitiltás visszavonása\",\n      \"noBans\": \"Senki nem lett még kitiltva\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Kilépés a szobából\",\n      \"confirmLeaveRoom\": \"Biztosan ki szeretne lépni?\",\n      \"leave\": \"Kilépés\",\n      \"inviteUsersToRoomBtn\": \"Felhasználók meghívása a szobába\",\n      \"invite\": \"Meghívás\",\n      \"toggleMuteMicBtn\": \"Mikrofon\",\n      \"mute\": \"Némítás\",\n      \"unmute\": \"Némítás feloldása\",\n      \"makeRoomPublicBtn\": \"Szoba publikussá állítása!\",\n      \"settings\": \"Beállítások\",\n      \"speaker\": \"Beszélők\",\n      \"listener\": \"Hallgatók\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Süketítés\",\n      \"deafen\": \"Süketítés\",\n      \"undeafen\": \"Süketítés visszavonása\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"A mikrofonhoz való hozzáférést megtagadták (lehet, hogy be kell lépnie a böngészőbeállításaiba és újra be kell töltenie az oldalt)\",\n      \"dismiss\": \"Elutasítás\",\n      \"tryAgain\": \"Próbálja újra\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Publikus\",\n        \"private\": \"Privát\",\n        \"roomName\": \"Szoba neve\",\n        \"roomDescription\": \"Szoba leírása\",\n        \"descriptionError\": \"Maximum 500 karakter\",\n        \"nameError\": \"Minimum 2, maximum 60 karakter\",\n        \"subtitle\": \"Töltse ki a mezőket a szoba létrehozásához\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Új szoba létrehozva\",\n        \"roomInviteFrom\": \"Meghívás tőle:\",\n        \"justStarted\": \"Éppen most kezdenek\",\n        \"likeToJoin\": \", Szeretne csatlakozni?\",\n        \"inviteReceived\": \"Meghívtak ide:\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Foglalt felhasználónév\",\n        \"avatarUrlError\": \"Helytelen kép\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord profilkép URL címe\",\n        \"displayNameError\": \"Minimum 2, maximum 50 karakter\",\n        \"displayNameLabel\": \"Megjelenített név\",\n        \"usernameError\": \"Minimum 4, maximum 15 karakter, csak alfanumerikus és aláhúzás\",\n        \"usernameLabel\": \"Felhasználónév\",\n        \"bioError\": \"Maximum 160 karakter\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter borítókép URL címe\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Biztosan tiltaná a felhasználót a szobához való csatlakozástól?\",\n        \"blockUser\": \"Felhasználó letiltása\",\n        \"makeMod\": \"Előreléptetés moderátorrá\",\n        \"unmod\": \"Visszaléptetés hallgatóvá\",\n        \"addAsSpeaker\": \"Beszélőkhöz adás\",\n        \"moveToListener\": \"Visszaléptetés hallgatóvá\",\n        \"banFromChat\": \"Kitiltás a chatről\",\n        \"banFromRoom\": \"Kitiltás a szobából\",\n        \"goBackToListener\": \"Visszalépés hallgatóvá\",\n        \"deleteMessage\": \"Üzenet törlése\",\n        \"makeRoomCreator\": \"Előreléptetés tulajdonossá\",\n        \"unBanFromChat\": \"Szoba kitiltás visszavonása\",\n        \"banIPFromRoom\": \"IP cím bannolása a szobából\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Beszélői engedélyre van szükség\",\n        \"makePublic\": \"Szoba publikussá állítása\",\n        \"makePrivate\": \"Szoba priváttá állítása\",\n        \"renamePublic\": \"Szoba publikus nevének beállítása\",\n        \"renamePrivate\": \"Szoba privát nevének beállítása\",\n        \"chatDisabled\": \"Chat kikapcsolása\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Bekapcsolva\",\n          \"disabled\": \"Kikapcsolva\",\n          \"followerOnly\": \"Csak követők\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Valamilyen okból nincs hallgató\" },\n    \"addToCalendar\": { \"add\": \"Felvétel naptárba\" },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Gyorsbillentyű beállítása\",\n      \"listening\": \"Hallgatás...\",\n      \"toggleMuteKeybind\": \"Némítás\",\n      \"togglePushToTalkKeybind\": \"Push-to-talk\",\n      \"toggleOverlayKeybind\": \"Átfedés gyorsgomb\",\n      \"toggleDeafKeybind\": \"Süketítés gyorsgomb\"\n    },\n    \"wsKilled\": {\n      \"description\": \"A Websocket kapcsolatot megszakította a szerver. Ez általában akkor fordul elő, amikor több fülön meg van nyitva a weboldal.\",\n      \"reconnect\": \"Újracsatlakozás\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Az eszköze jelenleg nem támogatott. Létrehozhat egy\",\n      \"linkText\": \"hibát\",\n      \"addSupport\": \"GitHubon, és megpróbáljuk biztosítani eszköze támogatottságát.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Meghívott\",\n      \"inviteToRoom\": \"Meghívás szobába\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Felhasználok\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Jelenleg nincsenek online barátai\",\n      \"showMore\": \"Továbbiak\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Ütemzett szobák\",\n      \"exploreMoreRooms\": \"További szobák felfedezése\"\n    },\n    \"search\": {\n      \"placeholder\": \"Kereshet szobákat, felhasználókat vagy kategóriákat\",\n      \"placeholderShort\": \"Keresés\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Nyelv\",\n      \"reportABug\": \"Hiba jelentése\",\n      \"useOldVersion\": \"Régi verzió\",\n      \"logOut\": {\n        \"button\": \"Kijelentkezés\",\n        \"modalSubtitle\": \"Biztosan kijelentkezne?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Audió hibakereső\",\n        \"stopDebugger\": \"Hibakereső leállítása\"\n      },\n      \"downloadApp\": \"App letöltése\",\n      \"developer\": \"Fejlesztői beállítások\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Hozzájáruló\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Üzenetek\",\n      \"showMore\": \"Több megjelenítése\",\n      \"noMessages\": \"Nincsenek új üzenetek\"\n    }\n  },\n  \"modules\": {\n    \"scheduledRooms\": {\n      \"title\": \"Ütemezett szobák\",\n      \"noneFound\": \"Szoba nem található\",\n      \"allRooms\": \"Összes ütemezett szoba\",\n      \"myRooms\": \"Ütemezett szobáim\",\n      \"scheduleRoomHeader\": \"Szoba ütemezése\",\n      \"startRoom\": \"Szoba indítása\",\n      \"modal\": {\n        \"needsFuture\": \"Jövőbeli dátum szükséges\",\n        \"roomName\": \"Szoba neve\",\n        \"minLength\": \"Minimum 2 karakter\",\n        \"roomDescription\": \"Leírás\"\n      },\n      \"tommorow\": \"HOLNAP\",\n      \"today\": \"MA\",\n      \"deleteModal\": { \"areYouSure\": \"Biztosan törölné az ütemzett szobát?\" }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotikonok hamarosan]\",\n      \"bannedAlert\": \"Kitiltottak a beszélgetésből\",\n      \"waitAlert\": \"Várnod kell egy másodpercet a következő üzenet küldése előtt.\",\n      \"search\": \"Keresés\",\n      \"searchResults\": \"Keresés eredménye\",\n      \"recent\": \"Gyakran használt\",\n      \"sendMessage\": \"Üzenet küldése\",\n      \"whisper\": \"Privát üzenet\",\n      \"welcomeMessage\": \"Üdv a beszélgetésben!\",\n      \"roomDescription\": \"Szoba leírása\",\n      \"disabled\": \"Chat letiltva\",\n      \"messageDeletion\": {\n        \"message\": \"Üzenet\",\n        \"retracted\": \"visszavonva\",\n        \"deleted\": \"törölve\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Rakéta feltöltése\",\n      \"takingOff\": \"Kilövés\",\n      \"inSpace\": \"Űrben\",\n      \"approachingMoon\": \"Hold megközelítése\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Nap megközelítése\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Galaxis megközelítése\",\n      \"galacticDoge\": \"Galaktikus Doge\",\n      \"spottedLife\": \"Élhető bolygó felfedezve\"\n    },\n    \"feed\": { \"yourFeed\": \"Hírfolyamod\" }\n  }\n}"
  },
  {
    "path": "kibbeh/public/locales/id/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Muat lebih banyak\",\n    \"loading\": \"Memuat...\",\n    \"noUsersFound\": \"Pengguna tidak ditemukan!\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ya\",\n    \"no\": \"Tidak\",\n    \"cancel\": \"Batal\",\n    \"save\": \"Simpan\",\n    \"edit\": \"Ubah\",\n    \"delete\": \"Hapus\",\n    \"joinRoom\": \"Gabung ke ruangan\",\n    \"copyLink\": \"Salin tautan\",\n    \"copied\": \"Disalin\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Mohon ingat menggunakan DogeHouse tanpa ijin aksesibilitas mungkin akan menimbulkan error yang tidak di inginkan\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Bisu | DogeHouse\",\n    \"deafenedTitle\": \"Menulikan | DogeHouse\",\n    \"dashboard\": \"Dasbor\",\n    \"connectionTaken\": \"Koneksi Diambil\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Kisah Awal\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Laporkan kesalahan\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Blokir\",\n      \"userStaffandContrib\": \"Kontrubsi pengguna & staf\",\n      \"staff\": \"Staf: \",\n      \"contributions\": \"Kontribusi\",\n      \"username\": \"Nama pengguna\",\n      \"usrStaff\": \"Pengguna yang staf\",\n      \"usrContributions\": \"Kontribusi pengguna\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Daftar pengguna yang tidak ada di ruangan privat dan kamu ikuti.\",\n      \"currentRoom\": \"Saat ini di:\",\n      \"startPrivateRoom\": \"Mulai ruangan privat dengan mereka\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Ikuti\",\n      \"followingHim\": \"Mengikuti\",\n      \"title\": \"People\",\n      \"followingNone\": \"Tidak mengikuti siapa pun\",\n      \"noFollowers\": \"Tidak ada pengikut\"\n    },\n    \"home\": {\n      \"createRoom\": \"Buat Ruangan\",\n      \"refresh\": \"Segarkan\",\n      \"editRoom\": \"Ubah Ruangan\",\n      \"desktopAlert\": \"Unduh aplikasi desktop DogeHouse hari ini!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Ruangan hilang, kembali\",\n      \"shareRoomLink\": \"Bagikan tautan ke ruangan\",\n      \"inviteFollowers\": \"Kamu bisa ajak pengikut kamu yang online:\",\n      \"whenFollowersOnline\": \"Saat pengikut Anda online, mereka akan muncul di sini.\"\n    },\n    \"login\": {\n      \"headerText\": \"Membawa obrolan suara ke bulan 🚀\",\n      \"featureText_1\": \"Mode Gelap\",\n      \"featureText_2\": \"Pendaftaran terbuka\",\n      \"featureText_3\": \"Dukungan Lintas Platform\",\n      \"featureText_4\": \"Sumber Terbuka\",\n      \"featureText_5\": \"Obrolan Teks\",\n      \"featureText_6\": \"Dipersembahkan oleh Doge\",\n      \"loginGithub\": \"Masuk dengan GitHub\",\n      \"loginTwitter\": \"Masuk dengan Twitter\",\n      \"createTestUser\": \"buat akun uji coba\",\n      \"loginDiscord\": \"Masuk dengan Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Keluar\",\n      \"probablyLoading\": \"mungkin sedang memuat...\",\n      \"voiceSettings\": \"Pengaturan suara\",\n      \"soundSettings\": \"Pengaturan bunyi\",\n      \"deleteAccount\": \"Hapus akun\",\n      \"overlaySettings\": \"Pergi ke pengaturan lapisan\",\n      \"couldNotFindUser\": \"Maaf, kami tidak bisa menemukan pengguna itu\",\n      \"privacySettings\": \"Pengaturan privasi\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oops! Halaman ini hilang dalam percakapan.\",\n      \"goHomeMessage\": \"Tak perlu khawatir. Kamu bisa\",\n      \"goHomeLinkText\": \"kembali ke awal\"\n    },\n    \"room\": {\n      \"speakers\": \"Pembicara\",\n      \"requestingToSpeak\": \"Mohon bicara\",\n      \"listeners\": \"Pendengar\",\n      \"allowAll\": \"Izinkan semua\",\n      \"allowAllConfirm\": \"Apakah anda yakin? Ini akan mengizinkan semua {{count}} permintaan pengguna untuk bicara\"\n    },\n    \"searchUser\": { \"search\": \"cari...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Bunyi\",\n      \"title\": \"Pengaturan Bunyi\",\n      \"playSound\": \"Memainkan suara\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Ubah profil\",\n      \"followsYou\": \"Mengikutimu\",\n      \"followers\": \"pengikut\",\n      \"following\": \"mengikuti\",\n      \"followHim\": \"Ikuti\",\n      \"followingHim\": \"Mengikuti\",\n      \"copyProfileUrl\": \"Salin url profil\",\n      \"urlCopied\": \"URL disalin ke papan klip\",\n      \"unfollow\": \"Berhenti mengikuti\",\n      \"about\": \"Tentang\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Tentang\",\n        \"rooms\": \"Ruangan\",\n        \"scheduled\": \"Terjadwal\",\n        \"recorded\": \"Terekam\",\n        \"clips\": \"Klip\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Blokir\",\n      \"unblock\": \"Berhenti blokir\",\n      \"sendDM\": \"Kirim DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"Kamu diblokir oleh pengguna ini.\",\n        \"default\": \"Whoops! Kami tidak bisa memuat pengguna ini.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Pengaturan Suara\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Mikrofon tidak ditemukan, mungkin kamu belum memasangnya atau belum memberikan website ini akses.\",\n      \"refresh\": \"Muat ulang daftar mikrofon\",\n      \"volume\": \"Suara:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Judul aplikasi tidak valid\",\n        \"label\": \"Memasukkan judul aplikasi\"\n      },\n      \"header\": \"Pengaturan lapisan\"\n    },\n    \"download\": {\n      \"starting\": \"Mengunduh...\",\n      \"failed\": \"Tidak bisa mengunduh otomatis, mohon coba lagi\",\n      \"visit_gh\": \"Kunjungi Rilis Github\",\n      \"prompt\": \"Tekan tombol di bawah untuk memulai mengunduh\",\n      \"download_now\": \"Unduh Sekarang\",\n      \"download_for\": \"Unduh untuk %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Pengaturan privasi\",\n      \"header\": \"Pengaturan privasi\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Pengguna diblokir\",\n      \"unban\": \"Berhenti blokir\",\n      \"noBans\": \"Tidak ada pengguna yang diblokir\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Keluar ruangan saat ini\",\n      \"confirmLeaveRoom\": \"Kamu yakin ingin keluar?\",\n      \"leave\": \"Keluar\",\n      \"inviteUsersToRoomBtn\": \"Undang pengguna ke ruangan\",\n      \"invite\": \"Undang\",\n      \"toggleMuteMicBtn\": \"Beralih mikrofon untuk membisu\",\n      \"mute\": \"Bisu\",\n      \"unmute\": \"Suarakan\",\n      \"makeRoomPublicBtn\": \"Buat ruangan publik!\",\n      \"settings\": \"Pengaturan\",\n      \"speaker\": \"Pembicara\",\n      \"listener\": \"Pendengar\",\n      \"chat\": \"Percakapan\",\n      \"toggleDeafMicBtn\": \"Beralih menulikan\",\n      \"deafen\": \"menulikan\",\n      \"undeafen\": \"Izinkan suara\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Perangkatmu saat ini belum didukung. Kamu bisa membuat\",\n      \"linkText\": \"isu di GitHub\",\n      \"addSupport\": \"dan kami akan coba menambahkan dukungan untuk perangkatmu.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Diundang!\",\n      \"inviteToRoom\": \"Undang ke ruangan\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Izin ditolak saat mencoba akses mikrofon (kamu mungkin bisa ke pengaturan browser dan memuat ulang halaman)\",\n      \"dismiss\": \"Hilangkan\",\n      \"tryAgain\": \"Coba lagi\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Beralih tombol\",\n      \"listening\": \"Menunggu tombol\",\n      \"toggleMuteKeybind\": \"Beralih tombol untuk membisu\",\n      \"togglePushToTalkKeybind\": \"Beralih tombol klik-untuk-bicara\",\n      \"toggleOverlayKeybind\": \"Beralih tombol lapisan\",\n      \"toggleDeafKeybind\": \"Beralih tombol menulikan\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Perangkat audio tidak ditemukan\" },\n    \"addToCalendar\": { \"add\": \"Tambahkan ke Kalender\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket dihentikan oleh server. Biasanya terjadi karena kamu membuka laman web ini di tab lain.\",\n      \"reconnect\": \"Menghubungkan kembali\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"publik\",\n        \"private\": \"privat\",\n        \"roomName\": \"nama ruangan\",\n        \"roomDescription\": \"deskripsi ruangan\",\n        \"descriptionError\": \"panjang maksimal 500\",\n        \"nameError\": \"panjang karakter antara 2 sampai 60\",\n        \"subtitle\": \"Isi kolom-kolom berikut untuk memulai ruangan baru\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Ruangan Baru Dibuat\",\n        \"roomInviteFrom\": \"Undangan Ruangan dari\",\n        \"justStarted\": \"Mereka baru saja mulai\",\n        \"likeToJoin\": \", maukah kamu bergabung?\",\n        \"inviteReceived\": \"kamu telah diundang ke\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Nama pengguna telah dipakai\",\n        \"avatarUrlError\": \"Gambar tidak valid\",\n        \"avatarUrlLabel\": \"URL avatar Github/Twitter/Discord\",\n        \"displayNameError\": \"Panjang 2 sampai 50 karakter\",\n        \"displayNameLabel\": \"Nama Tampilan\",\n        \"usernameError\": \"Panjang 4 sampai 15 karakter dan hanya alfanumerik/garis bawah\",\n        \"usernameLabel\": \"Nama pengguna\",\n        \"bioError\": \"Maksimal 160 karakter\",\n        \"bioLabel\": \"Biodata\",\n        \"bannerUrlLabel\": \"Spanduk Twitter URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Anda yakin memblokir pengguna ini untuk bergabung ke ruangan yang anda buat?\",\n        \"blockUser\": \"Blokir pengguna\",\n        \"makeMod\": \"Jadikan moderator\",\n        \"unmod\": \"Berhenti menjadi moderator\",\n        \"addAsSpeaker\": \"Tambahkan sebagai pembicara\",\n        \"moveToListener\": \"Pindah ke pendengar\",\n        \"banFromChat\": \"Blokir dari percakapan\",\n        \"banFromRoom\": \"Blokir dari ruangan\",\n        \"goBackToListener\": \"Kembali ke pendengar\",\n        \"deleteMessage\": \"Hapus pesan ini\",\n        \"makeRoomCreator\": \"Buat admin ruangan\",\n        \"unBanFromChat\": \"Bolehkan di percakapan\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"membutuhkan izin untuk berbicara\",\n        \"makePublic\": \"buat ruangan jadi publik\",\n        \"makePrivate\": \"buat ruangan jadi privat\",\n        \"renamePublic\": \"Ubah nama ruangan publik\",\n        \"renamePrivate\": \"Ubah nama ruangan privat\",\n        \"chatDisabled\": \"nonaktifkan percakapan\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Percakapan\",\n          \"enabled\": \"Diaktifkan\",\n          \"disabled\": \"Tidak diaktifkan\",\n          \"followerOnly\": \"Pengikut saja\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Orang-orang\",\n      \"online\": \"AKTIF\",\n      \"noOnline\": \"Kamu tidak mempunyai teman yang sedang aktif\",\n      \"showMore\": \"Tampilkan lebih banyak\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Ruangan yang akan datang\",\n      \"exploreMoreRooms\": \"Jelajahi ruangan lain\"\n    },\n    \"search\": {\n      \"placeholder\": \"Cari pengguna, ruangan, atau kategori\",\n      \"placeholderShort\": \"Cari\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Bahasa\",\n      \"reportABug\": \"Laporkan Kesalahan\",\n      \"useOldVersion\": \"Pakai Versi Lama\",\n      \"logOut\": {\n        \"button\": \"Keluar\",\n        \"modalSubtitle\": \"Apakah kamu yakin ingin keluar?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Berhentikan Debugger\"\n      },\n      \"downloadApp\": \"Unduh aplikasi\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staf\",\n      \"dhContributor\": \"DogeHouse Kontributer\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Pesan\",\n      \"showMore\": \"Menampilkan lebih banyak\",\n      \"noMessages\": \"Tidak ada pesan yang baru\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Ruangan Terjadwal\",\n      \"noneFound\": \"tidak ditemukan\",\n      \"allRooms\": \"seluruh ruangan terjadwal\",\n      \"myRooms\": \"ruangan terjadwal saya\",\n      \"scheduleRoomHeader\": \"Jadwalkan Ruangan\",\n      \"startRoom\": \"mulai ruangan\",\n      \"modal\": {\n        \"needsFuture\": \"dibutuhkan di masa depan\",\n        \"roomName\": \"nama ruangan\",\n        \"minLength\": \"minimal panjang 2\",\n        \"roomDescription\": \"Deskripsi\"\n      },\n      \"tommorow\": \"BESOK\",\n      \"today\": \"HARI INI\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Apakah kamu yakin ingin menghapus ruangan yang sudah terjadwal ini?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Percakapan\",\n      \"emotesSoon\": \"[emoji segera]\",\n      \"bannedAlert\": \"Kamu dilarang dari percakapan\",\n      \"waitAlert\": \"Kamu harus menunggu sebentar sebelum mengirim pesan lain\",\n      \"search\": \"Cari\",\n      \"searchResults\": \"Hasil Pencarian\",\n      \"recent\": \"Sering Digunakan\",\n      \"sendMessage\": \"Kirim Pesan\",\n      \"whisper\": \"Bisikan\",\n      \"welcomeMessage\": \"Selamat datang di percakapan!\",\n      \"roomDescription\": \"Deskripsi ruangan\",\n      \"disabled\": \"percakapan sudah dinonaktifkan\",\n      \"messageDeletion\": {\n        \"message\": \"pesan\",\n        \"retracted\": \"ditarik kembali\",\n        \"deleted\": \"dihapus\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Mengisi bahan bakar roket\",\n      \"takingOff\": \"Lepas landas\",\n      \"inSpace\": \"Di ruang angkasa\",\n      \"approachingMoon\": \"Mendekati bulan\",\n      \"lunarDoge\": \"Doge di bulan\",\n      \"approachingSun\": \"Mendekati matahari\",\n      \"solarDoge\": \"Doge di matahari\",\n      \"approachingGalaxy\": \"Mendekati galaksi\",\n      \"galacticDoge\": \"Doge di galaksi\",\n      \"spottedLife\": \"Planet dengan kehidupan ditemukan\"\n    },\n    \"feed\": { \"yourFeed\": \"Feed kamu\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/is/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"hlaða meira\",\n    \"loading\": \"hleður...\",\n    \"noUsersFound\": \"engir notendur fundust\",\n    \"ok\": \"ok\",\n    \"yes\": \"já\",\n    \"no\": \"nei\",\n    \"cancel\": \"hætta við\",\n    \"save\": \"vista\",\n    \"edit\": \"breyta\",\n    \"delete\": \"eyða\",\n    \"joinRoom\": \"taktu þátt\",\n    \"copyLink\": \"afrita hlekk\",\n    \"copied\": \"afritað\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Þaggaður | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Upprunasaga\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Tilkynna vandamál\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Listi yfir notendur sem eru ekki í einkaherbergi og þú fylgir.\",\n      \"currentRoom\": \"núna í\",\n      \"startPrivateRoom\": \"stofnaðu einkaspjallrásin með þeim\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Stofna Spjallrás\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"rásin horfið, farðu tíl baka\",\n      \"shareRoomLink\": \"deila hlekknum í herbergið\",\n      \"inviteFollowers\": \"Þú getur boðið fylgjendum þínum sem eru á netinu:\",\n      \"whenFollowersOnline\": \"Þegar fylgjendur þínir eru tengdir, munu þeir birtast hér.\"\n    },\n    \"login\": {\n      \"headerText\": \"Tekur raddsamræður til tunglsins🚀\",\n      \"featureText_1\": \"Næturþema\",\n      \"featureText_2\": \"Opið fyrir nýja notendur\",\n      \"featureText_3\": \"Styður flest tæki\",\n      \"featureText_4\": \"Opinn hugbúnaður\",\n      \"featureText_5\": \"Textaspjall\",\n      \"featureText_6\": \"Knúið af Doge\",\n      \"loginGithub\": \"innskráning með GitHub\",\n      \"loginTwitter\": \"innskráning með Twitter\",\n      \"createTestUser\": \"búa til prófanotanda\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"skrá út\",\n      \"probablyLoading\": \"hleður...(líklega)\",\n      \"voiceSettings\": \"farðu í raddstillingar\",\n      \"soundSettings\": \"farðu í hljóðstillingar\",\n      \"deleteAccount\": \"eyða reikningi\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Úps! Þessi síða týndist í samtali.\",\n      \"goHomeMessage\": \"Ekki hafa áhyggjur. Þú getur\",\n      \"goHomeLinkText\": \"farið heim\"\n    },\n    \"room\": {\n      \"speakers\": \"Hátalarar\",\n      \"requestingToSpeak\": \"Biðja um að tala\",\n      \"listeners\": \"Hlustendur\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"leita...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Hljóðin\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"breyta prófíl\",\n      \"followsYou\": \"fylgir þér\",\n      \"followers\": \"fylgjendur\",\n      \"following\": \"eftirfarandi\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Talstillingar\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"engir hljóðnemar fundust, kannski hefurðu það ekki tengt eða hefur ekki gefið leyfi til að nota það.\",\n      \"refresh\": \"endurnýja hljóðnemalistann\",\n      \"volume\": \"hljóðstyrkur:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bannaðir Notendur\",\n      \"unban\": \"unban\",\n      \"noBans\": \"Enginn hefur verið bannaður ennþá\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Yfirgefa spjallrásina\",\n      \"confirmLeaveRoom\": \"Ertu viss um að þú viljir yfirgefa rásina?\",\n      \"leave\": \"Yfirgefa\",\n      \"inviteUsersToRoomBtn\": \"Bjóða notendum í spjallrásina\",\n      \"invite\": \"Bjóða\",\n      \"toggleMuteMicBtn\": \"Slökkva á hljóðnema\",\n      \"mute\": \"Þagga niður\",\n      \"unmute\": \"Afþagga\",\n      \"makeRoomPublicBtn\": \"Opinbera rás!\",\n      \"settings\": \"Stillingar\",\n      \"speaker\": \"Ræðumaður\",\n      \"listener\": \"Hlustandi\",\n      \"chat\": \"Spjall\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Tækið þitt er sem stendur ekki stutt. Þú getur búið til\",\n      \"linkText\": \"beiðni á GitHub\",\n      \"addSupport\": \"og þá verður reynt að bæta við stuðningi fyrir tækið þitt.\"\n    },\n    \"inviteButton\": { \"invited\": \"boðið\", \"inviteToRoom\": \"bjóða í spjallrás\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Leyfi hafnað þegar reynt var að fá aðgang að hljóðnemanum þínum (þú gætir þurft að fara í vafrastillingar og endurhlaða síðuna)\",\n      \"dismiss\": \"segja upp\",\n      \"tryAgain\": \"reyna aftur\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"stilltu lyklabindingu\",\n      \"listening\": \"er að hlusta\",\n      \"toggleMuteKeybind\": \"afþagga\",\n      \"togglePushToTalkKeybind\": \"slökkva á 'Ýttu til að tala'\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"enginn hljóðnotandi af einhverjum ástæðum\"\n    },\n    \"addToCalendar\": { \"add\": \"Bæta við dagatal\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket tengingin var drepinn af netþjóninum. Þetta gerist venjulega þegar þú opnar vefsíðuna á öðrum flipa.\",\n      \"reconnect\": \"endurræsa tengingu\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Opin rás\",\n        \"private\": \"Einkarás\",\n        \"roomName\": \"Rásarheiti\",\n        \"roomDescription\": \"Rásarlýsing\",\n        \"descriptionError\": \"hámarks lengd 500\",\n        \"nameError\": \"verður að vera á bilinu 2 til 60 stafir\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nýtt herbergi búið til\",\n        \"roomInviteFrom\": \"Herbergisboð frá\",\n        \"justStarted\": \"Byrjað rétt í þessu\",\n        \"likeToJoin\": \", vilt þú bætast í hópinn?\",\n        \"inviteReceived\": \"þér hefur verið boðið í\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"notendanafn tekið\",\n        \"avatarUrlError\": \"Ógild mynd\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar slóð\",\n        \"displayNameError\": \"lengd 2 til 50 stafir\",\n        \"displayNameLabel\": \"Sýna nafn\",\n        \"usernameError\": \"lengd 4 til 15 stafir og aðeins tölustafur / undirstrikun\",\n        \"usernameLabel\": \"Notendanafn\",\n        \"bioError\": \"hámarkslengd 160 stafir\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ertu viss um að þú viljir hindra þennan notanda í að fara í hvaða herbergi sem þú stofnar?\",\n        \"blockUser\": \"loka á notanda\",\n        \"makeMod\": \"gera að moderator\",\n        \"unmod\": \"taka moderator réttindi\",\n        \"addAsSpeaker\": \"gera að mælanda\",\n        \"moveToListener\": \"færa til hlustenda\",\n        \"banFromChat\": \"banna frá spjalli\",\n        \"banFromRoom\": \"banna frá rásinni\",\n        \"goBackToListener\": \"fara aftur til hlustandans\",\n        \"deleteMessage\": \"eyða skilaboðum\",\n        \"makeRoomCreator\": \"gera að rásarstjórnenda\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"þurfa leyfi til að tala\",\n        \"makePublic\": \"opinbera rás\",\n        \"makePrivate\": \"gera að einkarás\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Skipulagðar Rásir\",\n      \"noneFound\": \"enginn fannst\",\n      \"allRooms\": \"öll ætlunarásirnar\",\n      \"myRooms\": \"áætlunarásirnar mínar\",\n      \"scheduleRoomHeader\": \"Skipuleggðu rásir\",\n      \"startRoom\": \"Stofna rás\",\n      \"modal\": {\n        \"needsFuture\": \"þarf að vera í framtíðinni\",\n        \"roomName\": \"rásarheiti\",\n        \"minLength\": \"lágmarkslengd 2\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Spjall\",\n      \"emotesSoon\": \"[emojis koma fljótlega]\",\n      \"bannedAlert\": \"Þú fékkst bann við spjalli\",\n      \"waitAlert\": \"Þú verður að bíða í sekúndu áður en þú sendir önnur skilaboð\",\n      \"search\": \"Leita\",\n      \"searchResults\": \"Leitarniðurstöður\",\n      \"recent\": \"Oft notað\",\n      \"sendMessage\": \"Senda skilaboð\",\n      \"whisper\": \"Hvísla\",\n      \"welcomeMessage\": \"Velkomin í spjallið!\",\n      \"roomDescription\": \"Rásarlýsing\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fylli á eldflaugina\",\n      \"takingOff\": \"Tek á loft\",\n      \"inSpace\": \"Út í geimi\",\n      \"approachingMoon\": \"Nálgast tunglið\",\n      \"lunarDoge\": \"Tungldoge\",\n      \"approachingSun\": \"Nálgast sólina\",\n      \"solarDoge\": \"Sóldoge\",\n      \"approachingGalaxy\": \"Nálgast vetrarbraut\",\n      \"galacticDoge\": \"Vetrarbrautar Doge\",\n      \"spottedLife\": \"Pláneta með lífi fundin\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/it/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Carica altro\",\n    \"loading\": \"Caricamento...\",\n    \"noUsersFound\": \"Nessun utente trovato\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Sì\",\n    \"no\": \"No\",\n    \"cancel\": \"Cancella\",\n    \"save\": \"Salva\",\n    \"edit\": \"Modifica\",\n    \"delete\": \"Elimina\",\n    \"joinRoom\": \"Partecipa alla stanza\",\n    \"copyLink\": \"Copia link\",\n    \"copied\": \"Copiato\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Si prega di notare che l'utilizzo di DogeHouse senza i permessi di accesso potrebbe causare degli errori\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Microfono disabilitato | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origini\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Segnala un bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Bandisci\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista di utenti che segui e che non sono in una stanza privata.\",\n      \"currentRoom\": \"In questo momento è connesso a:\",\n      \"startPrivateRoom\": \"Avvia una stanza privata con lui\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Segui\",\n      \"followingHim\": \"Segui già\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Crea una stanza\",\n      \"refresh\": \"Ricarica\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"La stanza non esiste, torna indietro\",\n      \"shareRoomLink\": \"Condividi link della stanza\",\n      \"inviteFollowers\": \"Seguaci online che puoi invitare:\",\n      \"whenFollowersOnline\": \"Quando i tuoi seguaci saranno online spunteranno qui.\"\n    },\n    \"login\": {\n      \"headerText\": \"Portiamo le conversazioni vocali fino alla luna 🚀\",\n      \"featureText_1\": \"Tema scuro\",\n      \"featureText_2\": \"Registrazioni aperte\",\n      \"featureText_3\": \"Supporto cross-platform\",\n      \"featureText_4\": \"Open source\",\n      \"featureText_5\": \"Chat di testo\",\n      \"featureText_6\": \"Offerto da Doge\",\n      \"loginGithub\": \"Accedi con Github\",\n      \"loginTwitter\": \"Accedi con Twitter\",\n      \"createTestUser\": \"Area utente per test\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Disconnetti\",\n      \"probablyLoading\": \"Probabilmente sto caricando...\",\n      \"voiceSettings\": \"Modifica impostazioni della voce\",\n      \"soundSettings\": \"Modifica impostazioni dei suoni\",\n      \"deleteAccount\": \"Elimina account\",\n      \"overlaySettings\": \"Vai alle impostazioni di sovrapposizione\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oops! questa pagina si è persa nella conversazione.\",\n      \"goHomeMessage\": \"Non preoccuparti. Puoi\",\n      \"goHomeLinkText\": \"tornare alla schermata principale\"\n    },\n    \"room\": {\n      \"speakers\": \"Oratori\",\n      \"requestingToSpeak\": \"Vogliono parlare\",\n      \"listeners\": \"Ascoltatori\",\n      \"allowAll\": \"Permetti a tutti\",\n      \"allowAllConfirm\": \"Sei sicuro? Questo permetterà a {{count}} che vogliono parlare di prendere la parola\"\n    },\n    \"searchUser\": { \"search\": \"Cerca...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Suoni\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Modifica profilo\",\n      \"followsYou\": \"Ti segue\",\n      \"followers\": \"Seguaci\",\n      \"following\": \"Seguiti\",\n      \"followHim\": \"Segui\",\n      \"followingHim\": \"Segui già\",\n      \"copyProfileUrl\": \"Copia link del profilo\",\n      \"urlCopied\": \"Link copiato negli appunti\",\n      \"unfollow\": \"Smetti di seguire\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Impostazioni della voce\",\n      \"mic\": \"Microfono:\",\n      \"permissionError\": \"Nessun microfono trovato, probabilmente non hai dato i permessi necessari a DogeHouse per poterlo usare o non ne hai attaccato nessuno.\",\n      \"refresh\": \"Ricarica la lista dei microfoni\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Titolo app invalido\",\n        \"label\": \"Inserisci titolo app\"\n      },\n      \"header\": \"Impostazioni sovrapposizione\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Utenti banditi\",\n      \"unban\": \"Riammetti\",\n      \"noBans\": \"Nessuno è stato ancora bandito\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Lascia questa stanza\",\n      \"confirmLeaveRoom\": \"Sei sicuro di voler lasciare la stanza?\",\n      \"leave\": \"Lascia\",\n      \"inviteUsersToRoomBtn\": \"Invita utenti alla stanza\",\n      \"invite\": \"Invita\",\n      \"toggleMuteMicBtn\": \"Abilita/disabilita microfono\",\n      \"mute\": \"Disabilita microfono\",\n      \"unmute\": \"Abilita microfono\",\n      \"makeRoomPublicBtn\": \"Fai diventare la stanza pubblica!\",\n      \"settings\": \"Impostazioni\",\n      \"speaker\": \"Oratore\",\n      \"listener\": \"Ascoltatori\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Il tuo dispositivo non è supportato. Puoi segnalare il problema\",\n      \"linkText\": \"su Github\",\n      \"addSupport\": \"e io proverò ad aggiungere il supporto per il tuo dispositivo.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invitato\",\n      \"inviteToRoom\": \"Invita alla stanza\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Impossibile accedere al microfono: permesso negato (potresti aver bisogno di modificare le impostazioni del browser e/o di riavviare la pagina)\",\n      \"dismiss\": \"Ignora\",\n      \"tryAgain\": \"Riprova di nuovo\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Imposta scorciatoia\",\n      \"listening\": \"Ascoltatore\",\n      \"toggleMuteKeybind\": \"Scorciatoia per attivare/disattivare il microfono\",\n      \"togglePushToTalkKeybind\": \"Scorciatoia per il push-to-talk\",\n      \"toggleOverlayKeybind\": \"Attiva/disattiva la schermata delle scorciatoie\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Nessun audio è disponibile per qualche motivo\"\n    },\n    \"addToCalendar\": { \"add\": \"Aggiungi al calendario\" },\n    \"wsKilled\": {\n      \"description\": \"Il websocket è stato messo fuori uso dal server. Questo accade solitamente quando riapri il sito in un'altra scheda.\",\n      \"reconnect\": \"Riconnettiti\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Pubblica\",\n        \"private\": \"Privata\",\n        \"roomName\": \"Nome della stanza\",\n        \"roomDescription\": \"Descrizione della stanza\",\n        \"descriptionError\": \"La lunghezza massima è 500\",\n        \"nameError\": \"Il nome deve essere lungo dai 2 ai 60 caratteri\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nuova stanza creata\",\n        \"roomInviteFrom\": \"Hai ricevuto un invito da\",\n        \"justStarted\": \"Hanno appena iniziato\",\n        \"likeToJoin\": \", ti piacerebbe entrare?\",\n        \"inviteReceived\": \"Sei stato invitato\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Nome utente già preso\",\n        \"avatarUrlError\": \"Immagine invalida\",\n        \"avatarUrlLabel\": \"Url dell'avatar di Github/Twitter/Discord\",\n        \"displayNameError\": \"Deve essere lungo dai 2 ai 50 caratteri\",\n        \"displayNameLabel\": \"Nome da mostrare agli altri utenti\",\n        \"usernameError\": \"Il nome utente deve essere lungo dai 4 ai 15 caratteri e può solo contenere caratteri alfanumerici e trattini bassi\",\n        \"usernameLabel\": \"Nome utente\",\n        \"bioError\": \"La lunghezza massima della biografia è di 160 caratteri\",\n        \"bioLabel\": \"Biografia\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Sei sicuro di voler bloccare questo utente? non potrà più entrare nelle stanze che crei.\",\n        \"blockUser\": \"Blocca utente\",\n        \"makeMod\": \"Attribuisci permessi di amministratore\",\n        \"unmod\": \"Rimuovi permessi di amministratore\",\n        \"addAsSpeaker\": \"Aggiungi come oratore\",\n        \"moveToListener\": \"Rendi un ascoltatore\",\n        \"banFromChat\": \"Bandisci dalla chat\",\n        \"banFromRoom\": \"Bandisci dalla stanza\",\n        \"goBackToListener\": \"Torna ad essere un ascoltatore\",\n        \"deleteMessage\": \"Elimina questo messaggio\",\n        \"makeRoomCreator\": \"Rendi amministratore della stanza\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Richiedi permesso per parlare\",\n        \"makePublic\": \"Rendi la stanza pubblica\",\n        \"makePrivate\": \"Rendi la stanza privata\",\n        \"renamePublic\": \"Imposta il nome della stanza pubblica\",\n        \"renamePrivate\": \"Imposta il nome della stanza privata\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Stanze pianificate\",\n      \"noneFound\": \"Nessuna stanza è stata trovata\",\n      \"allRooms\": \"Tutte le stanze pianificate\",\n      \"myRooms\": \"Le mie stanze pianificate\",\n      \"scheduleRoomHeader\": \"Pianifica una stanza\",\n      \"startRoom\": \"Avvia stanza\",\n      \"modal\": {\n        \"needsFuture\": \"Deve essere nel futuro\",\n        \"roomName\": \"Nome della stanza\",\n        \"minLength\": \"La lunghezza minima è 2\",\n        \"roomDescription\": \"Descrizione\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[le emoticon saranno disponibili in futuro]\",\n      \"bannedAlert\": \"Sei stato bandito dalla chat\",\n      \"waitAlert\": \"Devi aspettare un secondo prima di mandare un altro messaggio in chat\",\n      \"search\": \"Cerca\",\n      \"searchResults\": \"Risultati della ricerca\",\n      \"recent\": \"Usati frequentemente\",\n      \"sendMessage\": \"Invia un messaggio\",\n      \"whisper\": \"Sussurra\",\n      \"welcomeMessage\": \"Benvenuto nella chat!\",\n      \"roomDescription\": \"Descrizione della stanza\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Rifornimento razzo\",\n      \"takingOff\": \"Decollo\",\n      \"inSpace\": \"Nello spazio\",\n      \"approachingMoon\": \"Avvicinamento alla luna\",\n      \"lunarDoge\": \"Doge lunare\",\n      \"approachingSun\": \"Avvicinamento al sole\",\n      \"solarDoge\": \"Doge solare\",\n      \"approachingGalaxy\": \"Avvicinamento alla galassia\",\n      \"galacticDoge\": \"Doge galattico\",\n      \"spottedLife\": \"Avvistato pianeta con forme di vita\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ja/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"読み込む\",\n    \"loading\": \"読込中...\",\n    \"noUsersFound\": \"ユーザーが見つかりませんでした\",\n    \"ok\": \"OK\",\n    \"yes\": \"はい\",\n    \"no\": \"いいえ\",\n    \"cancel\": \"キャンセル\",\n    \"save\": \"保存\",\n    \"edit\": \"編集\",\n    \"delete\": \"削除\",\n    \"joinRoom\": \"ルームに参加\",\n    \"copyLink\": \"リンクをコピー\",\n    \"copied\": \"コピーしました\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"ミュート | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origin Story\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"バグを報告\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"BAN\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"フォローしていて、非公開ルームには参加していないユーザー\",\n      \"currentRoom\": \"参加中:\",\n      \"startPrivateRoom\": \"非公開ルームを開始\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"ルーム作成\",\n      \"refresh\": \"更新\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ルームが削除されました。前のページに戻ります\",\n      \"shareRoomLink\": \"リンクを共有\",\n      \"inviteFollowers\": \"オンラインのユーザーを招待:\",\n      \"whenFollowersOnline\": \"フォロワーがオンラインになるとここに表示されます\"\n    },\n    \"login\": {\n      \"headerText\": \"月を目指して会話しよう！ 🚀\",\n      \"featureText_1\": \"ダークテーマ\",\n      \"featureText_2\": \"参加自由\",\n      \"featureText_3\": \"クロス プラットフォーム\",\n      \"featureText_4\": \"オープンソース\",\n      \"featureText_5\": \"テキストチャット\",\n      \"featureText_6\": \"提供: Doge\",\n      \"loginGithub\": \"GitHub でログイン\",\n      \"loginTwitter\": \"Twitter でログイン\",\n      \"createTestUser\": \"テストユーザーを作成\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"ログアウト\",\n      \"probablyLoading\": \"きっと読込中...\",\n      \"voiceSettings\": \"マイク設定\",\n      \"soundSettings\": \"スピーカー設定\",\n      \"deleteAccount\": \"アカウント削除\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"おっと！？ このページは会話に紛れてしまいました。\",\n      \"goHomeMessage\": \"ご心配なく。\",\n      \"goHomeLinkText\": \"ホームに戻りましょう。\"\n    },\n    \"room\": {\n      \"speakers\": \"スピーカー\",\n      \"requestingToSpeak\": \"発言をリクエスト\",\n      \"listeners\": \"リスナー\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"検索...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"音声設定\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"プロフィールを編集\",\n      \"followsYou\": \"フォローされています\",\n      \"followers\": \"フォロワー\",\n      \"following\": \"フォロー\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"マイク設定\",\n      \"mic\": \"マイク:\",\n      \"permissionError\": \"マイクが見つかりませんでした。 マイクが接続されていることを確認し、アクセスを許可してください。\",\n      \"refresh\": \"更新\",\n      \"volume\": \"ボリューム:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"BANされたユーザー\",\n      \"unban\": \"BANを解除\",\n      \"noBans\": \"BANされたユーザーはいません\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"ルームから退出する\",\n      \"confirmLeaveRoom\": \"ルームから退出しますか？\",\n      \"leave\": \"退出\",\n      \"inviteUsersToRoomBtn\": \"ユーザーを招待する\",\n      \"invite\": \"招待\",\n      \"toggleMuteMicBtn\": \"ミュート切り替え\",\n      \"mute\": \"ミュート\",\n      \"unmute\": \"ミュート解除\",\n      \"makeRoomPublicBtn\": \"ルームを公開\",\n      \"settings\": \"設定\",\n      \"speaker\": \"スピーカー\",\n      \"listener\": \"リスナー\",\n      \"chat\": \"チャット\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"デバイスはサポートされていません。\",\n      \"linkText\": \"GitHubでissueを作成\",\n      \"addSupport\": \"するとサポートを受けられます。\"\n    },\n    \"inviteButton\": { \"invited\": \"招待\", \"inviteToRoom\": \"ルームに招待\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"マイクへのアクセスが許可されていません（ブラウザーの設定を変更して、ページを再読み込みする必要があるかもしれません）\",\n      \"dismiss\": \"無視\",\n      \"tryAgain\": \"リトライ\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"ショートカットキーを設定\",\n      \"listening\": \"聞いています\",\n      \"toggleMuteKeybind\": \"ミュート切り替え\",\n      \"togglePushToTalkKeybind\": \"プッシュトゥートーク切り替え\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"何らかの原因で音声が出ません\" },\n    \"addToCalendar\": { \"add\": \"カレンダーに追加\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocketがサーバーによって強制終了されました。 これは通常、別のタブで開いたときに発生します。\",\n      \"reconnect\": \"再接続\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"公開\",\n        \"private\": \"非公開\",\n        \"roomName\": \"名前\",\n        \"roomDescription\": \"説明\",\n        \"descriptionError\": \"500文字以内\",\n        \"nameError\": \"2文字から60文字で設定してください\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"ルームが作成されました\",\n        \"roomInviteFrom\": \"ルームに招待されました\",\n        \"justStarted\": \"作成者:\",\n        \"likeToJoin\": \" | 参加しますか？\",\n        \"inviteReceived\": \"招待者:\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"ユーザーネームトークン\",\n        \"avatarUrlError\": \"画像が正しくありません\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord 画像URL\",\n        \"displayNameError\": \"2～50文字で設定してください\",\n        \"displayNameLabel\": \"表示名\",\n        \"usernameError\": \"4～15文字で設定してください（英数字・アンダーバーのみ）\",\n        \"usernameLabel\": \"ユーザー名\",\n        \"bioError\": \"160文字以内にしてください\",\n        \"bioLabel\": \"自己紹介\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"このユーザーがあなたの作成したすべてのルームに参加するのを禁止しても良いですか？\",\n        \"blockUser\": \"ブロック\",\n        \"makeMod\": \"モデレーターに設定\",\n        \"unmod\": \"モデレーターを解除\",\n        \"addAsSpeaker\": \"スピーカーとして追加\",\n        \"moveToListener\": \"リスナーに変更\",\n        \"banFromChat\": \"チャットからBAN\",\n        \"banFromRoom\": \"ルームからBAN\",\n        \"goBackToListener\": \"リスナーに戻る\",\n        \"deleteMessage\": \"メッセージを削除\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"発言を許可制にする\",\n        \"makePublic\": \"ルームを公開に変更\",\n        \"makePrivate\": \"ルームを非公開に変更\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"開催予定のルーム\",\n      \"noneFound\": \"見つかりませんでした\",\n      \"allRooms\": \"すべて\",\n      \"myRooms\": \"自分が作成したルーム\",\n      \"scheduleRoomHeader\": \"開催予定を追加\",\n      \"startRoom\": \"ルームを開始\",\n      \"modal\": {\n        \"needsFuture\": \"現在より後の日時を指定してください\",\n        \"roomName\": \"名前\",\n        \"minLength\": \"2文字以上の名前を指定してください\",\n        \"roomDescription\": \"説明\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"チャット\",\n      \"emotesSoon\": \"[絵文字がまもなく設定可能に]\",\n      \"bannedAlert\": \"チャットからBANされました\",\n      \"waitAlert\": \"次のメッセージを送る前に少しお待ちください\",\n      \"search\": \"検索\",\n      \"searchResults\": \"検索結果\",\n      \"recent\": \"最近\",\n      \"sendMessage\": \"メッセージを送信\",\n      \"whisper\": \"ささやく\",\n      \"welcomeMessage\": \"チャットへようこそ！\",\n      \"roomDescription\": \"ルームの説明\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"燃料補給中\",\n      \"takingOff\": \"発射\",\n      \"inSpace\": \"現在、宇宙空間\",\n      \"approachingMoon\": \"まもなく月\",\n      \"lunarDoge\": \"月犬 / Lunar Doge\",\n      \"approachingSun\": \"まもなく太陽\",\n      \"solarDoge\": \"太陽犬 / Solar Doge\",\n      \"approachingGalaxy\": \"まもなく銀河系\",\n      \"galacticDoge\": \"銀河系犬 / Galactic Doge\",\n      \"spottedLife\": \"新たな生命を発見\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/kk/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"тағы да жүктеу\",\n    \"loading\": \"жүктелуде...\",\n    \"noUsersFound\": \"қолданушылар табылмады\",\n    \"ok\": \"ОК\",\n    \"yes\": \"иә\",\n    \"no\": \"жоқ\",\n    \"cancel\": \"артқа\",\n    \"save\": \"сақтау\",\n    \"edit\": \"өзгерту\",\n    \"delete\": \"жою\",\n    \"joinRoom\": \"Бөлмеге қосылу\",\n    \"copyLink\": \"Сілтемені көшіру\",\n    \"copied\": \"Көшірілді\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Қол жетімділік рұқсатынсыз DogeHouseты қолдану керексіз қателіктер тудыруы мүмкін екенін ескеріңіз\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Үнсіз | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Жасалу оқиғасы\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Қателер туралы хабарлау\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"бан\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Жабық бөлмеде емес сіз жазылған қолданушылар тізімі\",\n      \"currentRoom\": \"қазір :\",\n      \"startPrivateRoom\": \"Олармен жабық бөлме ашу\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"жазылу\",\n      \"followingHim\": \"Сіз жазылдыңыз\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Бөлме ашу\",\n      \"refresh\": \"Жаңарту\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Бөлме жойылып кетті, артқа қайтыңыз\",\n      \"shareRoomLink\": \"Бөлменің сілтемесімен бөлісу\",\n      \"inviteFollowers\": \"Дәл қазір желідегі жазылмандарыңызды шақыра аласыз:\",\n      \"whenFollowersOnline\": \"Жазылмандарыңыз желіде болғанда осы жерде пайда болады\"\n    },\n    \"login\": {\n      \"headerText\": \"Дауыстық байланысты айға дейін жеткіземіз 🚀\",\n      \"featureText_1\": \"Қараңғы режим\",\n      \"featureText_2\": \"Ашық тіркелі\",\n      \"featureText_3\": \"Кроссплатформалылық\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Текстілік чат\",\n      \"featureText_6\": \"Doge арқылы жұмыс істейді\",\n      \"loginGithub\": \"GitHub арқылы кіру\",\n      \"loginTwitter\": \"Twitter арқылы кіру\",\n      \"createTestUser\": \"Текстілік қолданушыны жасау\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"шығу\",\n      \"probablyLoading\": \"Сірә, жүктелуде...\",\n      \"voiceSettings\": \"дауыс баптауларына өту\",\n      \"soundSettings\": \"дыбыс баптауларына өту\",\n      \"deleteAccount\": \"аккаунтты жою\",\n      \"overlaySettings\": \"Қабаттасу (overlay) баптауларына өту\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Қап! Бұл бет сөйлесіп жатқанда жойылып кетіпті.\",\n      \"goHomeMessage\": \"Тек алаңдамаңыз! Сіз келесі қадамды істей аласыз\",\n      \"goHomeLinkText\": \"Басты бетке қайту\"\n    },\n    \"room\": {\n      \"speakers\": \"Спикерлер\",\n      \"requestingToSpeak\": \"Сөйлегісі келетіндер\",\n      \"listeners\": \"Тыңдармандар\",\n      \"allowAll\": \"Барлығына рұқсат беру\",\n      \"allowAllConfirm\": \"Бұл барлық рұқсат сұраған {{count}} қолданушыға, сөйлеуге мүмкіндік береді.Сіз сенімдісіз бе?\"\n    },\n    \"searchUser\": { \"search\": \"іздеу...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"дыбыстар\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Профайлды өңдеу\",\n      \"followsYou\": \"сізге жазылған\",\n      \"followers\": \"жазылмандар\",\n      \"following\": \"қолданушысына жазылған\",\n      \"followHim\": \"жазылу\",\n      \"followingHim\": \"Сіз жазылғансыз\",\n      \"copyProfileUrl\": \"Профайл сілтемесімен көшіру\",\n      \"urlCopied\": \"Сілтеме көшірілді\",\n      \"unfollow\": \"Жазылуды доғару\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Дауыс баптамалары\",\n      \"mic\": \"микрофон:\",\n      \"permissionError\": \"микрофоныз таба алмадық, ол қосылмаған немесе браузеріңізге қажетті рұқсат берілмеген.\",\n      \"refresh\": \"микрофондар тізімін жаңарту\",\n      \"volume\": \"Қаттылығы:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Қолданба тақырыбы жарамсыз\",\n        \"label\": \"Қолданба тақырыбын еңгізіңіз\"\n      },\n      \"header\": \"Қабаттасу (Overlay) баптамалары\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Бұғатталған қолданушылар\",\n      \"unban\": \"Бұғаттауды доғару\",\n      \"noBans\": \"Ешкім бұғатталмаған\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Қазіргу бөлмеден шығып кету\",\n      \"confirmLeaveRoom\": \"Шығып кететіңізге сенімдісіз бе?\",\n      \"leave\": \"Шығу\",\n      \"inviteUsersToRoomBtn\": \"Бөлмеге қолданушыларды шақыру\",\n      \"invite\": \"Шақыру\",\n      \"toggleMuteMicBtn\": \"Микрофон режимін ауыстыру\",\n      \"mute\": \"Дыбысты өшіру\",\n      \"unmute\": \"Дыбысты қосу\",\n      \"makeRoomPublicBtn\": \"Бөлмені ашық бөлмегу айналдыру!\",\n      \"settings\": \"Баптамалар\",\n      \"speaker\": \"Спикер\",\n      \"listener\": \"Тыңдармандар\",\n      \"chat\": \"Чат\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Өкінішке орай, сіздің құрылғыға қолдау көрсетілмейді. Сіз келесіні жасай аласыз\",\n      \"linkText\": \"GitHub арқылы сұраныс жасауығызға болады\",\n      \"addSupport\": \"сонда сіздің құрылғыңызға қолдау көрсетумен айналысіға тырысамыз.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"шақырылған\",\n      \"inviteToRoom\": \"бөлмеге шақыру\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Микрофоныңызды қолдану мүмкін емес (сіз браузердің баптамаларын өзгертуіңіз керек немесе жай ғана бетті қайта жүктеп көріңіз)\",\n      \"dismiss\": \"бетін қайтарып тастау\",\n      \"tryAgain\": \"қайта көріңіз\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"пернені атау\",\n      \"listening\": \"тыңдалуда\",\n      \"toggleMuteKeybind\": \"Микрофонды осы пернені басқанда қосу\",\n      \"togglePushToTalkKeybind\": \"Осы пернені басып сөйлеулң қосу\",\n      \"toggleOverlayKeybind\": \"Қабаттасуды осы перне арқылы өшіріп/қосу\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"белгісіз себептермен дыбыс жоқ\" },\n    \"addToCalendar\": { \"add\": \"Күнтізбеге қосу\" },\n    \"wsKilled\": {\n      \"description\": \"Вебсокетті сервер жойды. Бұл әдетте сайтты басқа бетте ашқан кезде болады.\",\n      \"reconnect\": \"қайтадан қосылу\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"жариялы\",\n        \"private\": \"жеке\",\n        \"roomName\": \"бөлме атауы\",\n        \"roomDescription\": \"бөлменің сипаттамасы\",\n        \"descriptionError\": \"ең көп дегенде 500 символ\",\n        \"nameError\": \"ұзындығы 2-ден 60 символға дейін болуы тиіс\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Жаңа бөлме жасалды\",\n        \"roomInviteFrom\": \"Бөлмеге шақыртуды келесі қолданушыдан алдыңыз\",\n        \"justStarted\": \"Олар жаңа ғана бастады\",\n        \"likeToJoin\": \", қосылғығыз келе ме?\",\n        \"inviteReceived\": \"Сіз келесі бөлмеге шақырту алдыңыз\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Бұл ат бос емес\",\n        \"avatarUrlError\": \"Суретіңіз жарамайды\",\n        \"avatarUrlLabel\": \"GitHub не Twitter аватарларының сілтемелері\",\n        \"displayNameError\": \"ұзындығы 2-ден 50 символға дейін\",\n        \"displayNameLabel\": \"Бұқаралық есім\",\n        \"usernameError\": \"ұзындығы 4-тен 15 символ болуы керек және олар тек әріптік/сандық символ немес төменгі сызық (_) бола алады\",\n        \"usernameLabel\": \"Қолданушы аты\",\n        \"bioError\": \"Тек 160 символға дейін ғана\",\n        \"bioLabel\": \"Мен туралы\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Бұл қолданушыға сіздің барлық бөлмелеріңізге кіруіне жол бермеуге сенімдісіз бе?\",\n        \"blockUser\": \"қолданушыны бұғаттау\",\n        \"makeMod\": \"модератор қылдыру\",\n        \"unmod\": \"модератор дәрежесінен айыру\",\n        \"addAsSpeaker\": \"Спикер ретінде қосу\",\n        \"moveToListener\": \"тыңдарман қылдыру\",\n        \"banFromChat\": \"осы чаттан бұғаттау\",\n        \"banFromRoom\": \"осы бөлмеден бұғаттау\",\n        \"goBackToListener\": \"тыңдарман болу\",\n        \"deleteMessage\": \"осы хабарламаны жою\",\n        \"makeRoomCreator\": \"бөлме админы қылдыру\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Мұнда сөйлеу үшін рұқсат алу қажет\",\n        \"makePublic\": \"бөлмені жариялы ету\",\n        \"makePrivate\": \"бөлмені жеке қылдыру\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Жоспарланған бөлмелер\",\n      \"noneFound\": \"табылмады\",\n      \"allRooms\": \"барлық жоспарланған бөлмелер тізімі\",\n      \"myRooms\": \"мен жоспарлған бөлмелер\",\n      \"scheduleRoomHeader\": \"Жоспарланған бөлмелер\",\n      \"startRoom\": \"бөлмені бастау\",\n      \"modal\": {\n        \"needsFuture\": \"кейін пайда болуы керек\",\n        \"roomName\": \"бөлменің атауы\",\n        \"roomDescription\": \"сипаттамасы\",\n        \"minLength\": \"Ең кем дегенде 2 символ\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Чат\",\n      \"emotesSoon\": \"[эмодзилер жуырда]\",\n      \"bannedAlert\": \"Сіз бұл чаттан бұғатталдыңыз\",\n      \"waitAlert\": \"Келесі хабарлама жібергенше, сәл күтіңіз\",\n      \"search\": \"Іздеу\",\n      \"searchResults\": \"Іздеу нәтижесі\",\n      \"recent\": \"Жиі қолданатындарыңыз\",\n      \"sendMessage\": \"хабарламаны жіберу\",\n      \"whisper\": \"Сыбырлау\",\n      \"welcomeMessage\": \"Чатқа қош келдіңіз!\",\n      \"roomDescription\": \"Бөлме сипаттамасы\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Зымыранға жанармай құйылуда\",\n      \"takingOff\": \"Ұшырылуда\",\n      \"inSpace\": \"Ғарышта\",\n      \"approachingMoon\": \"Айға сәл қалуда\",\n      \"lunarDoge\": \"Айлық doge\",\n      \"approachingSun\": \"Күнге жетіп қалды\",\n      \"solarDoge\": \"Күндік doge\",\n      \"approachingGalaxy\": \"Галактика шегіне сәл қалды\",\n      \"galacticDoge\": \"Галакалық doge\",\n      \"spottedLife\": \"Өмірі бар планета анықталды\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ko/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"더 로드하기\",\n    \"loading\": \"로딩중...\",\n    \"noUsersFound\": \"사용자를 찾지 못했습니다\",\n    \"ok\": \"확인\",\n    \"yes\": \"예\",\n    \"no\": \"아니오\",\n    \"cancel\": \"취소\",\n    \"save\": \"저장\",\n    \"edit\": \"편집\",\n    \"delete\": \"삭제\",\n    \"joinRoom\": \"방 들어가기\",\n    \"copyLink\": \"링크 복사\",\n    \"copied\": \"복사됨\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"적절한 권한이 부여되지 않은 채로 Dogehouse를 실행하면 오류가 발생할 수 있습니다.\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"음소거 | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"사이트 역사\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"버그 보고\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"유저 차단\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"비공개 채팅방에 있지 않은 팔로우하는 사람들.\",\n      \"currentRoom\": \"채팅방:\",\n      \"startPrivateRoom\": \"비공개 채팅방에 입장하기.\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"방 만들기\",\n      \"refresh\": \"새로고침\",\n      \"editRoom\": \"방 수정하기\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"채팅방이 삭제됨. 이전 페이지로 돌아가기.\",\n      \"shareRoomLink\": \"채팅방 링크 공유\",\n      \"inviteFollowers\": \"현재 온라인인 팔로워 초대하기:\",\n      \"whenFollowersOnline\": \"팔로워가 온라인 상태이면 여기에 표시됩니다.\"\n    },\n    \"login\": {\n      \"headerText\": \"음성 채팅으로 달 까지 가봅시다!🚀\",\n      \"featureText_1\": \"어두운 테마\",\n      \"featureText_2\": \"등록 하기\",\n      \"featureText_3\": \"크로스 플랫폼 지원\",\n      \"featureText_4\": \"오픈 소스\",\n      \"featureText_5\": \"문자 채팅\",\n      \"featureText_6\": \"멍멍이의 힘으로 구동됨 \",\n      \"loginGithub\": \"깃허브로 로그인\",\n      \"loginTwitter\": \"트위터로 로그인\",\n      \"createTestUser\": \"테스트 계정 만들기\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"로그아웃\",\n      \"probablyLoading\": \"아마도 로딩 중...\",\n      \"voiceSettings\": \"음성 설정 들어가기\",\n      \"soundSettings\": \"소리 설정 들어가기\",\n      \"deleteAccount\": \"계정 삭제\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"아이고! 페이지가 사라졌습니다\",\n      \"goHomeMessage\": \"걱정할 필요 없습니다,\",\n      \"goHomeLinkText\": \"홈페이지로 돌아 가기 \"\n    },\n    \"room\": {\n      \"speakers\": \"스피커\",\n      \"requestingToSpeak\": \"발언권 요청\",\n      \"listeners\": \"청취자\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"검색...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"소리\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"프로필 변경\",\n      \"followsYou\": \"나를 팔로우하는 사람들\",\n      \"followers\": \"팔로워들\",\n      \"following\": \"팔로우하는 사람들\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"프로필 URL 복사\",\n      \"urlCopied\": \"URL이 클립보드에 복사 되었습니다.\",\n      \"unfollow\": \"팔로우 취소\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"음성 설정\",\n      \"mic\": \"마이크:\",\n      \"permissionError\": \"마이크를 찾을 수 없거나, 연결되지 않았거나, 이 페이지에서 마이크를 사용할 수있는 권한이 없습니다.\",\n      \"refresh\": \"마이크 목록 새로코침\",\n      \"volume\": \"음량:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"유효하지 않은 앱 타이틀 입니다.\",\n        \"label\": \"앱 타이틀 입력하기\"\n      },\n      \"header\": \"오버레이 설정\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"차단된 사용자들\",\n      \"unban\": \"차단해제\",\n      \"noBans\": \"아직 아무도 차단되지 않았습니다.\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"채팅방 나가기\",\n      \"confirmLeaveRoom\": \"정말 방에서 나가시겠습니까?\",\n      \"leave\": \"나가기\",\n      \"inviteUsersToRoomBtn\": \"채팅방에 유저 초대\",\n      \"invite\": \"초대\",\n      \"toggleMuteMicBtn\": \"마이크 음소거 토글\",\n      \"mute\": \"음소거\",\n      \"unmute\": \"음소거 취소\",\n      \"makeRoomPublicBtn\": \"채팅방 공개 하기!\",\n      \"settings\": \"설정\",\n      \"speaker\": \"스피커\",\n      \"listener\": \"청취자\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"현재 기기는 지원되지 않습니다, \",\n      \"linkText\": \"깃허브에 이슈를 생성하시면\",\n      \"addSupport\": \"빠른 시일 내에 기기에 대한 지원을 추가하겠습니다\"\n    },\n    \"inviteButton\": { \"invited\": \"초대됨\", \"inviteToRoom\": \"초대하기\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"마이크 액세스가 거부되었습니다 (설정을 변경하고 페이지를 새로 고침해야 할 수도 있음)\",\n      \"dismiss\": \"무시하기\",\n      \"tryAgain\": \"다시 시도하기\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"키바인드 설정\",\n      \"listening\": \"듣는중\",\n      \"toggleMuteKeybind\": \"음소거 키 토글\",\n      \"togglePushToTalkKeybind\": \"푸시 투 토크 키 토글\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"오디오 컨수머를 찾을수 없었습니다\"\n    },\n    \"addToCalendar\": { \"add\": \"달력에 추가\" },\n    \"wsKilled\": {\n      \"description\": \"웹 소켓 연결이 해제되었습니다. 주로 다른 탭에서 웹사이트를 여는 경우에 발생하는 오류 입니다.\",\n      \"reconnect\": \"다시 연결\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"공개\",\n        \"private\": \"비공개\",\n        \"roomName\": \"채팅방 이름\",\n        \"roomDescription\": \"채팅방 설명\",\n        \"descriptionError\": \"최대 길이 500자\",\n        \"nameError\": \"2자에서 60자 사이로 설정해주세요\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"새로운 채팅방이 생성 되었습니다\",\n        \"roomInviteFrom\": \"채팅방에서 초대\",\n        \"justStarted\": \"채팅방이 생성되었습니다\",\n        \"likeToJoin\": \",들어가시겠습니까 ?\",\n        \"inviteReceived\": \"초대된 방 이름\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"사용자 이름이 이미 채택되었습니다\",\n        \"avatarUrlError\": \"사진이 잘몼되었습니다\",\n        \"avatarUrlLabel\": \"깃허브/트위터 아바타 url\",\n        \"displayNameError\": \"2자에서 50자 사이\",\n        \"displayNameLabel\": \"표시된 이름\",\n        \"usernameError\": \"길이는 4 ~ 15 자 이내 여야하며 문자, 숫자 또는 밑줄 만 허용됩니다.\",\n        \"usernameLabel\": \"사용자 이름\",\n        \"bioError\": \"최대 길이는 160 자입니다.\",\n        \"bioLabel\": \"개인 프로필\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"이 사람을 블랙리스트에 추가 하시겠습니까? (다시는 당신이 생성 ​​한 채팅방에 참여할 수 없습니다).\",\n        \"blockUser\": \"유저 차단\",\n        \"makeMod\": \"관리자로 설정\",\n        \"unmod\": \"관리자 자격 박탈\",\n        \"addAsSpeaker\": \"스피커로 추가\",\n        \"moveToListener\": \"청취자로 이동\",\n        \"banFromChat\": \"채팅 차단\",\n        \"banFromRoom\": \"채팅방 에서 차단\",\n        \"goBackToListener\": \"청취자로 돌아가기\",\n        \"deleteMessage\": \"이 메세지 삭제\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"발언권이 필요합니다\",\n        \"makePublic\": \"채팅방 공개화\",\n        \"makePrivate\": \"채팅방 비공개화\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"예약 된 채팅방\",\n      \"noneFound\": \"찾지 못함\",\n      \"allRooms\": \"예약 된 채팅방 전부\",\n      \"myRooms\": \"내가 예약한 채팅방\",\n      \"scheduleRoomHeader\": \"채팅방 예약\",\n      \"startRoom\": \"채팅방 시작\",\n      \"modal\": {\n        \"needsFuture\": \"시간을 미래로 설정하세요\",\n        \"roomName\": \"채팅방 이름\",\n        \"minLength\": \"최소 2자\",\n        \"roomDescription\": \"설명\"\n      },\n      \"tommorow\": \"내일\",\n      \"today\": \"오늘\",\n      \"deleteModal\": { \"areYouSure\": \"정말 예약 된 방을 제거하시겠습니까?\" }\n    },\n    \"roomChat\": {\n      \"title\": \"문자 채팅\",\n      \"emotesSoon\": \"[이모티콘 업데이트중]\",\n      \"bannedAlert\": \"문자 채팅에서 금지되었습니다.\",\n      \"waitAlert\": \"다음 메시지를 보내기 전에 잠시 기다려야합니다.\",\n      \"search\": \"검색\",\n      \"searchResults\": \"검색 결과\",\n      \"recent\": \"자주 사용됨\",\n      \"sendMessage\": \"메세지 보내기\",\n      \"whisper\": \"속삭이기\",\n      \"welcomeMessage\": \"챗에 오신걸 환영합니다!\",\n      \"roomDescription\": \"방 설명\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"로켓 연료 주입중..\",\n      \"takingOff\": \"이륙!\",\n      \"inSpace\": \"우주 속..\",\n      \"approachingMoon\": \"달에 근접하는 중..\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"태양에 근접하는 중..\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"은하계에 근접하는 중..\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"생명체가 있는 새로운 행성에 도착\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ku/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"زیاتر ببینە\",\n    \"loading\": \"دابەزاندن...\",\n    \"noUsersFound\": \"هیچ کەسێك نەدۆزرایەوە\",\n    \"ok\": \"ئۆکەی\",\n    \"yes\": \"بەڵێ\",\n    \"no\": \"نەخێر\",\n    \"cancel\": \"رەتکردنەوە\",\n    \"save\": \"پاراستن\",\n    \"edit\": \"هەموارکردن\",\n    \"delete\": \"سڕینەوە\",\n    \"joinRoom\": \"پەیوەندیکردن بە ژوورەوە\",\n    \"copyLink\": \"کۆپیکردنی لینك\",\n    \"copied\": \"کۆپیکرا!\",\n    \"copy\": \"کۆپی\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"تکایە ئاگاداربە، کارکردن بە دۆجهاوس بێ مۆڵەتی چونەناوەوە دەکرێت هەڵەی نەخوازراوی بەدوادابێت\",\n    \"error\": \"هەڵە\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"دۆجهاوس\",\n    \"dashboard\": \"داشبۆرد\",\n    \"connectionTaken\": \"پەیوەندی راییکرا\",\n    \"mutedTitle\": \"بێ ئاخاوتن | دۆجهاوس\",\n    \"deafenedTitle\": \"بێدەنگکردن | دۆجهاوس\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"چیرۆکی دەستپێك\",\n    \"link_2\": \"دیسکۆرد\",\n    \"link_3\": \"هۆشداری کێشەی تەکنیکی\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    },\n    \"admin\": {\n      \"ban\": \"Ban\",\n      \"userStaffandContrib\": \"ستافی بەکارهێنەر و بەشداران\",\n      \"staff\": \"ستاف: \",\n      \"contributions\": \"بەشداران\",\n      \"username\": \"ناوی بەکارهێنەر\",\n      \"usrStaff\": \"ستافی بەکارهێنەران\",\n      \"usrContributions\": \"بەشداریی بەکارهێنەر\",\n      \"reason\": \"هۆکار\",\n      \"usernamePlaceholder\": \"بەکارهێنەر کار لەم بوارەدا دەکات\"\n    },\n    \"download\": {\n      \"prompt\": \"بەرنامەی کۆمپیوتەری دۆجهاوس داببەزێنە\",\n      \"download_for\": \"Download for %platform% (%ext%)\",\n      \"failed\": \"Unable to detect platform, please try again later or visit GitHub Releases\",\n      \"visit_gh\": \"Visit GitHub Releases\"\n    },\n    \"followingOnlineList\": {\n      \"title\": \"کەس\",\n      \"listHeader\": \"لیستی ئەو بەکارهێنەرانەی لە ژوورێکی تایبەتدا نین و تۆ ئاشنای پێیان.\",\n      \"currentRoom\": \"Currently in:\",\n      \"startPrivateRoom\": \"لە ژوورێکی تایبەتدا ئاخاوتن بکەن\"\n    },\n    \"followList\": {\n      \"title\": \"کەس\",\n      \"followHim\": \"ئاشنایەتی\",\n      \"followingHim\": \"ئاشنای بە\",\n      \"followingNone\": \"ئاشنای کەس نیە\",\n      \"noFollowers\": \"کەس ئاشنای نیە\"\n    },\n    \"home\": {\n      \"createRoom\": \"ژووری نوێ\",\n      \"editRoom\": \"هەمواری ژوورەکە بکە\",\n      \"refresh\": \"نوێکردنەوە\",\n      \"desktopAlert\": \"ئەمڕۆ بەرنامەی کۆمپیوتەری دۆجهاوس داببەزێنە!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ژوورەکە نەماوە؛ بگەڕێرەوە\",\n      \"shareRoomLink\": \"لینکی ژوورەکە شەیر بکە\",\n      \"inviteFollowers\": \"دەتوانی بانگێشتی ئەو ئاشنایانەت بکەیت کە لەسەر هێڵن:\",\n      \"whenFollowersOnline\": \"کاتێك ئاشنایانت لەسەر هێڵن، لێرە دەردەکەون: \"\n    },\n    \"login\": {\n      \"headerText\": \"ئاخاوتنی دەنگی تا سەر مانگ 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Login with GitHub\",\n      \"loginTwitter\": \"Login with Twitter\",\n      \"loginDiscord\": \"Login with Discord\",\n      \"createTestUser\": \"Create Test User\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Logout\",\n      \"probablyLoading\": \"probably loading...\",\n      \"voiceSettings\": \"Voice Settings\",\n      \"overlaySettings\": \"Overlay Settings\",\n      \"soundSettings\": \"Sound Settings\",\n      \"deleteAccount\": \"Delete Account\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy Settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\n      \"goHomeMessage\": \"Not to worry. You can\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": {\n        \"label\": \"Whispers\",\n        \"on\": \"On\",\n        \"off\": \"Off\"\n      }\n    },\n    \"room\": {\n      \"speakers\": \"Speakers\",\n      \"requestingToSpeak\": \"Requesting to Speak\",\n      \"listeners\": \"Listeners\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": {\n      \"search\": \"search...\"\n    },\n    \"soundEffectSettings\": {\n      \"title\": \"Sound Settings\",\n      \"header\": \"Sounds\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Edit Profile\",\n      \"followsYou\": \"Follows you\",\n      \"followers\": \"followers\",\n      \"following\": \"following\",\n      \"followHim\": \"Follow\",\n      \"unfollow\": \"Unfollow\",\n      \"followingHim\": \"Following\",\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"copyProfileUrl\": \"Copy Profile URL\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"aboutSuffix\": \"\",\n      \"bot\": \"Bot\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n\n        \"default\": \"Whoops! We couldn't load this user.\"\n      },\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      }\n    },\n    \"voiceSettings\": {\n      \"title\": \"Voice Settings\",\n      \"header\": \"Voice Settings\",\n      \"mic\": \"Mic:\",\n      \"permissionError\": \"No mics found, you either have none plugged in or haven't given this website permission.\",\n      \"refresh\": \"Refresh Mic List\",\n      \"volume\": \"Volume:\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter a valid app title\",\n        \"label\": \"App title\"\n      }\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Banned Users\",\n      \"unban\": \"Unban\",\n      \"noBans\": \"No one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Leave Current Room\",\n      \"confirmLeaveRoom\": \"Are you sure you want to leave?\",\n      \"leave\": \"Leave\",\n      \"inviteUsersToRoomBtn\": \"Invite Users to Room\",\n      \"invite\": \"Invite\",\n      \"toggleMuteMicBtn\": \"Toggle Mute\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\",\n      \"makeRoomPublicBtn\": \"Make Room Public!\",\n      \"settings\": \"Settings\",\n      \"speaker\": \"Speaker\",\n      \"listener\": \"Listener\",\n      \"chat\": \"Chat\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Your device is currently not supported. You can create an\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"and I will try adding support for your device.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invited!\",\n      \"inviteToRoom\": \"Invite to room\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permission denied trying to access your mic (you may need to go into browser settings and reload the page)\",\n      \"dismiss\": \"Dismiss\",\n      \"tryAgain\": \"Try Again\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Set Keybind\",\n      \"listening\": \"Listening...\",\n      \"toggleMuteKeybind\": \"Toggle mute keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\",\n      \"toggleOverlayKeybind\": \"Toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"Toggle push-to-talk keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"No audio consumer for some reason\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore more rooms\"\n    },\n    \"addToCalendar\": {\n      \"add\": \"Add to Calendar\"\n    },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"Reconnect\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"modals\": {\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      },\n      \"createRoomModal\": {\n        \"subtitle\": \"Fill the following fields to start a new room\",\n        \"public\": \"Public\",\n        \"private\": \"Private\",\n        \"roomName\": \"Room name\",\n        \"roomDescription\": \"Room description\",\n        \"descriptionError\": \"Max length 500\",\n        \"nameError\": \"Must be between 2 to 60 characters long\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"New Room Created\",\n        \"roomInviteFrom\": \"Room Invite from\",\n        \"justStarted\": \"They just started\",\n        \"likeToJoin\": \", would you like to join?\",\n        \"inviteReceived\": \"you've been invited to\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar URL\",\n        \"bannerUrlLabel\": \"Twitter banner URL\",\n        \"displayNameError\": \"Length 2 to 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"Length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"Max length of 160 characters\",\n        \"bioLabel\": \"Bio\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Are you sure you want to block this user from joining any room you ever create?\",\n        \"blockUser\": \"Block user\",\n        \"makeMod\": \"Promote to Mod\",\n        \"unmod\": \"Demote from Mod\",\n        \"addAsSpeaker\": \"Add as Speaker\",\n        \"moveToListener\": \"Move to Listener\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banFromChat\": \"Ban from Chat\",\n        \"banFromRoom\": \"Ban from Room\",\n        \"banIPFromRoom\": \"Ban IP from Room\",\n        \"goBackToListener\": \"Go Back to Listener\",\n        \"deleteMessage\": \"Delete this Message\",\n        \"makeRoomCreator\": \"Promote to Admin\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"require permission to speak\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        },\n        \"makePublic\": \"make room public\",\n        \"makePrivate\": \"make room private\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\"\n      }\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": {\n      \"yourFeed\": \"Your feed\"\n    },\n    \"scheduledRooms\": {\n      \"title\": \"Scheduled Rooms\",\n      \"noneFound\": \"none found\",\n      \"allRooms\": \"all scheduled rooms\",\n      \"myRooms\": \"my scheduled rooms\",\n      \"scheduleRoomHeader\": \"Schedule Room\",\n      \"startRoom\": \"start room\",\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"modal\": {\n        \"needsFuture\": \"needs to be in the future\",\n        \"roomName\": \"room name\",\n        \"roomDescription\": \"Description\",\n        \"minLength\": \"min length 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"You have been banned from chat\",\n      \"waitAlert\": \"You have to wait a second before sending another message\",\n      \"search\": \"Search\",\n      \"searchResults\": \"Search Results\",\n      \"recent\": \"Frequently Used\",\n      \"sendMessage\": \"Send a Message\",\n      \"whisper\": \"Whisper\",\n      \"welcomeMessage\": \"Welcome to chat!\",\n      \"roomDescription\": \"Room description\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/li/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Mieer laaje\",\n    \"loading\": \"Laaje...\",\n    \"noUsersFound\": \"Gein gebroekers gevonge\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Joa\",\n    \"no\": \"Nhee\",\n    \"cancel\": \"Annulere\",\n    \"save\": \"Opsjloan\",\n    \"edit\": \"Bewirke\",\n    \"delete\": \"Verwijdere\",\n    \"joinRoom\": \"Kamer ingoan\",\n    \"copyLink\": \"Link kopieeren\",\n    \"copied\": \"Gekopieerd\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Gemute | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ontstoan\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Probleem melde\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Banne\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Liest van gebroekers die dich volgst mèr niet in inge perseunlijke kamer zitten.\",\n      \"currentRoom\": \"Op t moment in:\",\n      \"startPrivateRoom\": \"Sjtart inge perseunlijke kamer mit disè persoon\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Kamer make\",\n      \"refresh\": \"Verviesje\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Kamer besjteed niet, gank teruk\",\n      \"shareRoomLink\": \"Link van disè kamer deijle\",\n      \"inviteFollowers\": \"Du kins dinge volgers die online sind oetneudigen\",\n      \"whenFollowersOnline\": \"Wannier dinge volgens online sind, komme ze hie te sjtoan.\"\n    },\n    \"login\": {\n      \"headerText\": \"Veer bringe gesjprekke noa de moan 🚀\",\n      \"featureText_1\": \"Donkere Thema\",\n      \"featureText_2\": \"Open Registraties\",\n      \"featureText_3\": \"Cross-Platform Ondersjteuning\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Tekst Chat\",\n      \"featureText_6\": \"Mögelik gemak durch Doge\",\n      \"loginGithub\": \"Log in mit GitHub\",\n      \"loginTwitter\": \"Log in mit Twitter\",\n      \"createTestUser\": \"Maak inge test gebroeker\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Oetlogge\",\n      \"probablyLoading\": \"Waarschinlik aan t laaje...\",\n      \"voiceSettings\": \"Gank noa spraakinsjtellinge\",\n      \"soundSettings\": \"Gank noa geluidsinstjtellinge\",\n      \"deleteAccount\": \"Account verwijdere\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Och nhee! Disè pagina is verlore gegange in t gesjprek.\",\n      \"goHomeMessage\": \"Mak niks oet. Du kins\",\n      \"goHomeLinkText\": \"teruk noa de homepagina\"\n    },\n    \"room\": {\n      \"speakers\": \"Sjprekers\",\n      \"requestingToSpeak\": \"Aanvroage om te sjpreke\",\n      \"listeners\": \"Loestereers\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"Zeucke...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Geluide\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profiel bewirke\",\n      \"followsYou\": \"Volgt dich\",\n      \"followers\": \"Volgers\",\n      \"following\": \"Volgend\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Sjpraak\",\n      \"mic\": \"Microfoon:\",\n      \"permissionError\": \"Ging microfoon gevonge, du hubs waarschinlik ging microfoon aangesjloate of ging toesjtimming gegive.\",\n      \"refresh\": \"Verviesj microfoonliest\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Gebanne Gebroekers\",\n      \"unban\": \"Unbanne\",\n      \"noBans\": \"Ginge is noch geband\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Verloat huidige kamer\",\n      \"confirmLeaveRoom\": \"Wit se zeker dat ze weg wils goan?\",\n      \"leave\": \"Verloate\",\n      \"inviteUsersToRoomBtn\": \"Nudig gebroekers oet veur disè kamer\",\n      \"invite\": \"Oetneudigen\",\n      \"toggleMuteMicBtn\": \"Microfoon aan- of oetzitte\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"makeRoomPublicBtn\": \"Maak kamer veur ideringe!\",\n      \"settings\": \"Insjtellinge\",\n      \"speaker\": \"Sjpreker\",\n      \"listener\": \"Loestereer\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Ding apparaat weurt neet ondersjteund. Du kins inge\",\n      \"linkText\": \"issue op GitHub\",\n      \"addSupport\": \"maken en ich zal ondersjteuning veur ding apparaat probeere toe te voegen.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Oetgeneudigd\",\n      \"inviteToRoom\": \"Oetneudigen veur kamer\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Du hubs ging toesjtimming gegive veur ding microfoon (du mos missjien in de insjtellinge van de browser goan en de pagina herlaaje)\",\n      \"dismiss\": \"Negeere\",\n      \"tryAgain\": \"Opnuj probere\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Sjneltoets insjtelle\",\n      \"listening\": \"Aan t loestere\",\n      \"toggleMuteKeybind\": \"Sjakel mute sjneltoets\",\n      \"togglePushToTalkKeybind\": \"Sjakel push-to-talk sjneltoets\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Ging audioapparaat veur ing of andere reje\"\n    },\n    \"addToCalendar\": { \"add\": \"Voeg toe aan agenda\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket is gejstopt durch de server. Dit gebeurt vaak wannier de site opensjteet in inge andere tabblad.\",\n      \"reconnect\": \"Opnuj verbinge\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Veur ideringe\",\n        \"private\": \"Perseunlijk\",\n        \"roomName\": \"Kamer naam\",\n        \"roomDescription\": \"Kamer umsjrieving\",\n        \"descriptionError\": \"Maximale lingte van 500\",\n        \"nameError\": \"Moet tusje de 2 en 60 karakters lang seen\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Neuje Kamer Gemak\",\n        \"roomInviteFrom\": \"Kamer Oetneudiging van\",\n        \"justStarted\": \"Ze sind net begonne\",\n        \"likeToJoin\": \", wils ze mit doan?\",\n        \"inviteReceived\": \"Du bis oetgeneudigd veur\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Gebroekersnaam al in gebroek\",\n        \"avatarUrlError\": \"Ongeldig plétje\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"2 tot 50 karakters\",\n        \"displayNameLabel\": \"Waargavenaam\",\n        \"usernameError\": \"4 tot 15 karakters en alling mer litters, ciefers en underscore\",\n        \"usernameLabel\": \"Gebroekersnaam\",\n        \"bioError\": \"Maximaal 160 karakters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Wit ze zeker dat du disè gebroeker wills blokkere van alle kamers die du ooit noch gees maken?\",\n        \"blockUser\": \"Gebroeker blokkere\",\n        \"makeMod\": \"Beheerde make\",\n        \"unmod\": \"Beheerder verwijdere\",\n        \"addAsSpeaker\": \"Às spjreker toevoege\",\n        \"moveToListener\": \"Verplaatsj noa sjpreker\",\n        \"banFromChat\": \"Banne van de chat\",\n        \"banFromRoom\": \"Banne van de kamer\",\n        \"goBackToListener\": \"Gank terug noa loestereer\",\n        \"deleteMessage\": \"Disè bericht verwijdere\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Toesjtimming neudig om te sjpreke\",\n        \"makePublic\": \"Maak kamer veur ideringe\",\n        \"makePrivate\": \"Maak kamer perseunlijke\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Geplende kamers\",\n      \"noneFound\": \"Ginge gevonge\",\n      \"allRooms\": \"Alle geplende kamers\",\n      \"myRooms\": \"Minge geplende kamers\",\n      \"scheduleRoomHeader\": \"Kamer plenne\",\n      \"startRoom\": \"Kamer sjtarte\",\n      \"modal\": {\n        \"needsFuture\": \"Mot in de toekomst seen\",\n        \"roomName\": \"Kamer naam\",\n        \"roomDescription\": \"Umsjrieving\",\n        \"minLength\": \"Minimale lingte van 2 karakters\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[Emotes binnekort]\",\n      \"bannedAlert\": \"Du bis van de chat geband\",\n      \"waitAlert\": \"Du mos ing seconde wachtte veurdat du wir ing bericht kins sjture.\",\n      \"search\": \"Zeucke\",\n      \"searchResults\": \"Zeukresultate\",\n      \"recent\": \"Vaak Gebroek\",\n      \"sendMessage\": \"Sjtuur ing bericht\",\n      \"whisper\": \"Floestere\",\n      \"welcomeMessage\": \"Welkom in de chat!\",\n      \"roomDescription\": \"Kamer umsjrieving\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Raket tanken\",\n      \"takingOff\": \"Opsjtiege\",\n      \"inSpace\": \"In de ruimte\",\n      \"approachingMoon\": \"Moan benajere\",\n      \"lunarDoge\": \"Moan hond\",\n      \"approachingSun\": \"Zon benajere\",\n      \"solarDoge\": \"Zonne hond\",\n      \"approachingGalaxy\": \"Heelal benajere\",\n      \"galacticDoge\": \"Galactische hond\",\n      \"spottedLife\": \"Planeet mit leefe gesjpot\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/lld/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Ciaria de plü\",\n    \"loading\": \"Ciariamënt...\",\n    \"noUsersFound\": \"Degun utënt ciafé\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ê\",\n    \"no\": \"No\",\n    \"cancel\": \"Anulëia\",\n    \"save\": \"Salva\",\n    \"edit\": \"Modifichëia\",\n    \"delete\": \"Eliminëia\",\n    \"joinRoom\": \"Partezipëia alla ciamena\",\n    \"copyLink\": \"Copiëia link\",\n    \"copied\": \"Copié\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"An ve prëia de notè che le utilise de DogeHouse zënza autorisaziuns de azes pudess gaujé fai\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Microfon dejabilité | DogeHouse\",\n    \"deafenedTitle\": \"Audio dejabilité | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Conesciun stabilida\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Origines\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Segnalëia n bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Banëia\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista de utënc che te stas do y che ne é nia te na ciamena privata.\",\n      \"currentRoom\": \"Te chësc mumënt este colié a:\",\n      \"startPrivateRoom\": \"Scomëncia na ciamena privata cun ël\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Stá do\",\n      \"followingHim\": \"Bele sté do\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Cheriëia na ciamena\",\n      \"refresh\": \"Ciaria danü\",\n      \"editRoom\": \"Modifichëia la ciamena\",\n      \"desktopAlert\": \"Desciaria la app desktop de DogeHouse incö!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"La ciamena ne esist nia, va zoruch\",\n      \"shareRoomLink\": \"Partësca le link dla ciamena\",\n      \"inviteFollowers\": \"Porsones che te vas do online che pos invié:\",\n      \"whenFollowersOnline\": \"Canche les porsones che te vas do é online, gnarai sö chiló.\"\n    },\n    \"login\": {\n      \"headerText\": \"Portun les conversaziuns a usc cina ala löna 🚀\",\n      \"featureText_1\": \"Tema scür\",\n      \"featureText_2\": \"Registraziuns davertes\",\n      \"featureText_3\": \"Suport sön plü plataformes\",\n      \"featureText_4\": \"Open source\",\n      \"featureText_5\": \"Discusciuns de test\",\n      \"featureText_6\": \"Ofrí da Doge\",\n      \"loginGithub\": \"Vá ite cun Github\",\n      \"loginTwitter\": \"Vá ite cun Twitter\",\n      \"loginDiscord\": \"Vá ite cun Discord\",\n      \"createTestUser\": \"Area utënt por proes\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Dejcoleghé\",\n      \"probablyLoading\": \"Plü dessigü sunsi bele tl laur da ciarié...\",\n      \"voiceSettings\": \"Modifichëia impostaziuns dla usc\",\n      \"soundSettings\": \"Modifichëia impostaziuns di sonns\",\n      \"deleteAccount\": \"Eliminëia account\",\n      \"overlaySettings\": \"Vá ales impostaziuns dl overlay\",\n      \"couldNotFindUser\": \"Al se desplej, ne un nia ciafé chël utënt\",\n      \"privacySettings\": \"Privacy Settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ostia! Chësta plata é jüda pordüda tla conversaziun.\",\n      \"goHomeMessage\": \"Nia te preocupé. Te pos\",\n      \"goHomeLinkText\": \"va zoruch ala plata prinzipala\"\n    },\n    \"room\": {\n      \"speakers\": \"Anunziadus\",\n      \"requestingToSpeak\": \"I ó baié\",\n      \"listeners\": \"Ascutadus\",\n      \"allowAll\": \"Conzedi a düc\",\n      \"allowAllConfirm\": \"Es sigü? Chësc conzedará a {{count}} utënc che ó baié de pudëi ciacolè\"\n    },\n    \"searchUser\": { \"search\": \"Chirida...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sonns\",\n      \"title\": \"Impostaziuns dl sonn\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Modifichëia profil\",\n      \"followsYou\": \"Te stá do\",\n      \"followers\": \"Chi che te stá do\",\n      \"following\": \"Chi che tö stas do\",\n      \"followHim\": \"Státi do\",\n      \"followingHim\": \"Ti stas bele do\",\n      \"copyProfileUrl\": \"Copiëia link dl profil\",\n      \"urlCopied\": \"Link copié tles anotaziuns\",\n      \"unfollow\": \"Lascia da sté do\",\n      \"about\": \"About\",\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Impostaziuns dla usc\",\n      \"mic\": \"Microfon:\",\n      \"permissionError\": \"Degun microfon ciafé, plü dessigü ne as nia dé les autorisaziuns nezesciaries a DogeHouse por podëi l'adoré o ne as nia un taché ite.\",\n      \"refresh\": \"Ciaria da nü la lista di microfons\",\n      \"volume\": \"Volum:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Titul aplicaziun invalid\",\n        \"label\": \"Inserësca titul aplicaziun\"\n      },\n      \"header\": \"Impostaziuns dl overlay\"\n    },\n    \"download\": {\n      \"starting\": \"Scomencé a desciarié...\",\n      \"failed\": \"Al ne é nia sté poscibl desciarié automaticamënter, prëibel proa indô n iade plü tert\",\n      \"visit_gh\": \"Vijitëia les emisciuns sön Github\",\n      \"prompt\": \"Clichëia söl buttun dessot por mët man la desciariada\",\n      \"download_now\": \"Desciaria sëgn\",\n      \"download_for\": \"Desciaria por %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Utënc bandis\",\n      \"unban\": \"Tol demez la bandida\",\n      \"noBans\": \"Degun utënt é sté ciamó bandí\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Lascia chësta ciamena\",\n      \"confirmLeaveRoom\": \"Es zigü de urëi lascié chësta ciamena?\",\n      \"leave\": \"Lascia\",\n      \"inviteUsersToRoomBtn\": \"Inviëia utënc tla ciamena\",\n      \"invite\": \"Inviëia\",\n      \"toggleMuteMicBtn\": \"Abilitëia/dejabilitëia microfon\",\n      \"mute\": \"Dejabilitëia microfon\",\n      \"unmute\": \"Abilitëia microfon\",\n      \"makeRoomPublicBtn\": \"Fá deventé chësta ciamena publica!\",\n      \"settings\": \"Impostaziuns\",\n      \"speaker\": \"Anunziadú\",\n      \"listener\": \"Ascutadú\",\n      \"chat\": \"Discusciun\",\n      \"toggleDeafMicBtn\": \"Abilitëia/dejabilitëia le audio\",\n      \"deafen\": \"Dejabilité le audio\",\n      \"undeafen\": \"Abilité le audio\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Le tü despositif ne é nia suporté. Te pos segnalé chësc problem\",\n      \"linkText\": \"sön Github\",\n      \"addSupport\": \"y porvaran da le mët a jí por le tü despositif.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invié\",\n      \"inviteToRoom\": \"Inviëia ala ciamena\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Imposcibl ciarié le microfon: autorisaziun negada (plü dessigü mësses modifiché les impostaziuns dl browser e/o ciarié la plata danü)\",\n      \"dismiss\": \"Ignorëia\",\n      \"tryAgain\": \"Proa ciamó n iade\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Modifichëia keybinds\",\n      \"listening\": \"Drüca val' botun!\",\n      \"toggleMuteKeybind\": \"Keybind por abilité/dejabilité l microfon\",\n      \"togglePushToTalkKeybind\": \"Keybind por l push-to-talk\",\n      \"toggleOverlayKeybind\": \"Keybind por abilité/dejabilité la schermata dles keybinds\",\n      \"toggleDeafKeybind\": \"Keybind por dejabilité le audio\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Degun audio é desponibl por val' rajun\"\n    },\n    \"addToCalendar\": { \"add\": \"Ajunta al calënder\" },\n    \"wsKilled\": {\n      \"description\": \"Le websocket é fora funziun dal server. Chësc sozed scialdi canche deures na plata danü te n'atra scheda.\",\n      \"reconnect\": \"Ciaria danü\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Publica\",\n        \"private\": \"Privata\",\n        \"roomName\": \"Inom dla ciamena\",\n        \"roomDescription\": \"Descriziun dla ciamena\",\n        \"descriptionError\": \"La lunghëza mascima è de 500 caratri\",\n        \"nameError\": \"Le inom mëss ester dai 2 ai 60 caratri\",\n        \"subtitle\": \"Implenësca les scatores dessot por cherié na nöia ciamena\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nöia ciamena cheriada\",\n        \"roomInviteFrom\": \"Te as ciafé n'invit da\",\n        \"justStarted\": \"Ë á apëna scomencé\",\n        \"likeToJoin\": \", oresses jí ite?\",\n        \"inviteReceived\": \"T'es sté invié\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Inom utënt bele adoré\",\n        \"avatarUrlError\": \"Imaja invalida\",\n        \"avatarUrlLabel\": \"URL dl avatar de Github/Twitter\",\n        \"displayNameError\": \"Mëss ester dai 2 ai 50 caratri\",\n        \"displayNameLabel\": \"Inom mustré ai atri utënc\",\n        \"usernameError\": \"Le inom utënt mëss ester dai 4 ai 15 caratteri, pó madër contigní caratri alfanumerics y righes basses\",\n        \"usernameLabel\": \"Inom utënt\",\n        \"bioError\": \"La lunghëza mascima dla biografia é de 160 caratri\",\n        \"bioLabel\": \"Biografia\",\n        \"bannerUrlLabel\": \"URL dl banner sön Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Es zigü che te os bloché chësc utënt? Ël ne puderá nia plö gní ite tles ciamenes cheriades da te.\",\n        \"blockUser\": \"Blochëia utënt\",\n        \"makeMod\": \"Fá deventé n aministradú\",\n        \"unmod\": \"Tola demez les autorisaziuns da aministradú\",\n        \"addAsSpeaker\": \"Fá deventé n anunziadú\",\n        \"moveToListener\": \"Fá deventé n ascutadú\",\n        \"banFromChat\": \"Bandësca dala discusciun\",\n        \"banFromRoom\": \"Bandësca dala ciamena\",\n        \"goBackToListener\": \"Va zoruch ad ester n ascutadú\",\n        \"deleteMessage\": \"Eliminëia chësc messaje\",\n        \"makeRoomCreator\": \"Fá deventé n aministradú tla ciamena\",\n        \"unBanFromChat\": \"Tola demez l ban por la discusciun\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Damana l autorisaziun por baié\",\n        \"makePublic\": \"Publichëia la ciamena\",\n        \"makePrivate\": \"Privatisëia la ciamena\",\n        \"renamePublic\": \"Modifichëia l inom dla ciamena publica\",\n        \"renamePrivate\": \"Modifichëia l inom dla ciamena privata\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Jënt\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Te as 0 amisc online sëgn\",\n      \"showMore\": \"Mostra de plü\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Ciamenes che á da gní\",\n      \"exploreMoreRooms\": \"Esplorëia plü ciamenes\"\n    },\n    \"search\": {\n      \"placeholder\": \"Chira ciamenes, utënc o categories\",\n      \"placeholderShort\": \"Chirí\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Lingaz\",\n      \"reportABug\": \"Segnalëia n bug\",\n      \"useOldVersion\": \"Adora la verjiun vedla\",\n      \"logOut\": {\n        \"button\": \"Vá fora\",\n        \"modalSubtitle\": \"Es zigü che te os jí fora dal account?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Archita Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Ciamenes planificades\",\n      \"noneFound\": \"Degüna ciamena é stada ciafada\",\n      \"allRooms\": \"Dütes les ciamenes planificades\",\n      \"myRooms\": \"Les tües ciamenes planificades\",\n      \"scheduleRoomHeader\": \"Planifichëia na ciamena\",\n      \"startRoom\": \"Mët a jí la ciamena\",\n      \"modal\": {\n        \"needsFuture\": \"Mëss ester tl futur\",\n        \"roomName\": \"Inom dla ciamena\",\n        \"minLength\": \"La lunghëza minima è de 2 caratri\",\n        \"roomDescription\": \"Descriziun\"\n      },\n      \"tommorow\": \"INDOMAN\",\n      \"today\": \"INCÖ\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Es zigü che te os eliminé chësta ciamenta planificada?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Discusciun\",\n      \"emotesSoon\": \"[les emoticon sará a desposiziun tl futur]\",\n      \"bannedAlert\": \"Te es sté bandí dala discusciun\",\n      \"waitAlert\": \"Mësses aspeté n secunt denant de mené n ater messaje tla discusciun\",\n      \"search\": \"Chirí\",\n      \"searchResults\": \"Resultac dla chirida\",\n      \"recent\": \"Adorá gonot\",\n      \"sendMessage\": \"Mené n messaje\",\n      \"whisper\": \"Mormorëia\",\n      \"welcomeMessage\": \"Benvenuto nella chat!\",\n      \"roomDescription\": \"Descriziun dla ciamena\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Refornimënt racheta\",\n      \"takingOff\": \"Aviada\",\n      \"inSpace\": \"Tl cosmos\",\n      \"approachingMoon\": \"Dlungia la lüna\",\n      \"lunarDoge\": \"Doge sön la löna\",\n      \"approachingSun\": \"Dlungia al sorëdl\",\n      \"solarDoge\": \"Doge dl sorëdl\",\n      \"approachingGalaxy\": \"Dlungia ala galassia\",\n      \"galacticDoge\": \"Doge dla galassia\",\n      \"spottedLife\": \"Afustié planët cun formes de vita\"\n    },\n    \"feed\": { \"yourFeed\": \"Tó feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/lt/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Pakrauti daugiau\",\n    \"loading\": \"Kraunasi...\",\n    \"noUsersFound\": \"Vartotojų nerasta!\",\n    \"ok\": \"Gerai\",\n    \"yes\": \"Taip\",\n    \"no\": \"Ne\",\n    \"cancel\": \"Atšaukti\",\n    \"save\": \"Išsaugoti\",\n    \"edit\": \"Redaguoti\",\n    \"delete\": \"Ištrinti\",\n    \"joinRoom\": \"Prisijungti prie kambario\",\n    \"copyLink\": \"Nukopijuoti nuorodą\",\n    \"copied\": \"Nukopijuota\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Nutildyta | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Kilmės istorija\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Pranešti apie klaidą\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Užblokuoti\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Sąrašas žmonių kurie nėra tavo privačiame kambaryje, tačiau tave seka\",\n      \"currentRoom\": \"Šiuo metu yra:\",\n      \"startPrivateRoom\": \"Sukurti privatų kambarį su jais\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Sukurti kambarį\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Kambario nebėra, grįžkite atgal\",\n      \"shareRoomLink\": \"Pasidalinkite nuoroda į kambarį\",\n      \"inviteFollowers\": \"Galite pakviesti savo sekėjus kurie yra prisijungę:\",\n      \"whenFollowersOnline\": \"Kada tavo sekėjai yra prisijungę, jie pasirodys čia.\"\n    },\n    \"login\": {\n      \"headerText\": \"Balso pokalbių nukėlimas į mėnulį 🚀\",\n      \"featureText_1\": \"Tamsi tema\",\n      \"featureText_2\": \"Atidarykite registraciją\",\n      \"featureText_3\": \"Kelių platformų palaikymas\",\n      \"featureText_4\": \"Atviro kodo\",\n      \"featureText_5\": \"Pokalbių kambarys\",\n      \"featureText_6\": \"Tvarkomas Doge\",\n      \"loginGithub\": \"Prisijunkite su GitHub\",\n      \"loginTwitter\": \"Prisijunkite su Twitter\",\n      \"createTestUser\": \"Sukurkite testavimo naudotoją\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Atsijungti\",\n      \"probablyLoading\": \"Tikriausiai kraunasi...\",\n      \"voiceSettings\": \"Eiti į balso nustatymus\",\n      \"soundSettings\": \"Eiti į garso nustatymus\",\n      \"deleteAccount\": \"Ištrinti vartotoją\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Uppss! Šis puslapis buvo prarastas pokalbyje\",\n      \"goHomeMessage\": \"Nesijaudink. Tu gali\",\n      \"goHomeLinkText\": \"grįžti namo\"\n    },\n    \"room\": {\n      \"speakers\": \"Kalbėtojai\",\n      \"requestingToSpeak\": \"Prašo kalbėti\",\n      \"listeners\": \"Klausytojai\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"Ieškoma...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Garsai\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Redaguoti profilį\",\n      \"followsYou\": \"Seka tave\",\n      \"followers\": \"Sekėjai\",\n      \"following\": \"Tu seki\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Garso nustatymai\",\n      \"mic\": \"mikrofonas:\",\n      \"permissionError\": \"nerasta mikrofonų, jūs arba neprijungėte tinklo, arba nedavėte šiai svetainei leidimo.\",\n      \"refresh\": \"Atnaujinti mikrofonų sąrašą\",\n      \"volume\": \"Garsas:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Užblokuoti vartotojai\",\n      \"unban\": \"Atblokuoti\",\n      \"noBans\": \"Niekas nebuvo užblokuotas kol kas\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Išeiti iš dabartinio kambario\",\n      \"confirmLeaveRoom\": \"Ar tikrai norite išeiti?\",\n      \"leave\": \"Išeiti\",\n      \"inviteUsersToRoomBtn\": \"Pakviesti vartotojus į kambarį\",\n      \"invite\": \"Pakviesti\",\n      \"toggleMuteMicBtn\": \"Perjungti nutildytą mikrofoną\",\n      \"mute\": \"Nutildyti\",\n      \"unmute\": \"įjungti garsą\",\n      \"makeRoomPublicBtn\": \"Padaryk kambarį viešu!\",\n      \"settings\": \"Nustatymai\",\n      \"speaker\": \"Kalbėtojas\",\n      \"listener\": \"Klausytojai\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Jūsų įrenginys šiuo metu nepalaikomas.\",\n      \"linkText\": \"Problema su GitHub\",\n      \"addSupport\": \"Pasistengsiu suteikti galimybę Jūsų įrenginiui\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Pakviestas\",\n      \"inviteToRoom\": \"Pakviesti į kambarį\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Leidimas atmestas bandant pasiekti jūsų mikrofoną (gali tekti pereiti į naršyklės nustatymus ir atnaujinti puslapį)\",\n      \"dismiss\": \"atmesti\",\n      \"tryAgain\": \"Bandykite iš naujo\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"nustatyti klavišą\",\n      \"listening\": \"Klausomasi\",\n      \"toggleMuteKeybind\": \"perjungti nutildytą klavišą\",\n      \"togglePushToTalkKeybind\": \"perjungti tiesioginio ryšio klavišą\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"dėl kokių nors priežasčių nėra garso vartotojo\"\n    },\n    \"addToCalendar\": { \"add\": \"Pridėti į kalendorių\" },\n    \"wsKilled\": {\n      \"description\": \"„Websocket“ užlaužtas serveris. Paprastai tai atsitinka, kai atidarote svetainę kitame skirtuke.\",\n      \"reconnect\": \"Prisijungti iš naujo\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Viešas\",\n        \"private\": \"Privatus\",\n        \"roomName\": \"Kambario pavadinimas\",\n        \"roomDescription\": \"Kambario aprašymas\",\n        \"descriptionError\": \"Didžiausias teksto ilgis yra 500 raidžių\",\n        \"nameError\": \"Turi būti nuo 2 iki 60 raidžių ilgio\",\n        \"subtitle\": \"Kad pradėtumėte kambarį, užpildykite formą\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Naujas kambarys sukurtas\",\n        \"roomInviteFrom\": \"Pakvietimas į kambarį nuo\",\n        \"justStarted\": \"Jie ką tik pradėjo\",\n        \"likeToJoin\": \", Ar norėtum prisijungti?\",\n        \"inviteReceived\": \"Tu buvai pakviestas į\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Vardas užimtas, panaudotas\",\n        \"avatarUrlError\": \"Negalima nuotrauka\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avataro nuoroda\",\n        \"displayNameError\": \"Ilgis nuo 2 iki 50 simbolių\",\n        \"displayNameLabel\": \"Rodyti vardą\",\n        \"usernameError\": \"Ilgis nuo 4 iki 15 simbolių ir tik raidinis(arba skaitmeninis)/apatinis pabraukimas\",\n        \"usernameLabel\": \"Slapyvardis\",\n        \"bioError\": \"Didžiausias ilgis - 160 simbolių\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ar tikrai norite užblokuoti šį vartotoją prisijungti prie bet kurio jūsų ateityje sukurto kambario?\",\n        \"blockUser\": \"Užblokuoti vartotoją\",\n        \"makeMod\": \"make mod\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"Pridėti, kaip kalbėtoją\",\n        \"moveToListener\": \"Perkelti į klausytojus\",\n        \"banFromChat\": \"Užblokuoti iš pokalbio\",\n        \"banFromRoom\": \"Užblokuoti iš kambario\",\n        \"goBackToListener\": \"Gražinti atgal į klausytojus\",\n        \"deleteMessage\": \"Ištrinti šią žinutę\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Reikalingas patvirtinimas kalbėti\",\n        \"makePublic\": \"Padaryti kambarį viešą\",\n        \"makePrivate\": \"Padaryti kambarį privatų\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Žmonės\",\n      \"online\": \"PRISIJUNGĘ\",\n      \"noOnline\": \"Nėra prisijungusių draugų\",\n      \"showMore\": \"Rodyti daugiau\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Būsimi kambariai\",\n      \"exploreMoreRooms\": \"Rodyti daugiau kambarių\"\n    },\n    \"search\": {\n      \"placeholder\": \"Ieškoti kambarių, vartotojų ar kategorijų\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profilis\",\n      \"language\": \"Kalba\",\n      \"reportABug\": \"Pranešti apie klaidą\",\n      \"useOldVersion\": \"Naudoti seną versiją\",\n      \"logOut\": {\n        \"button\": \"Atsijungti\",\n        \"modalSubtitle\": \"Ar tikrai norite atsijungti?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Suplanuoti kambariai\",\n      \"noneFound\": \"Nieko nerasta\",\n      \"allRooms\": \"Visi suplanuoti kambariai\",\n      \"myRooms\": \"Mano suplanuoti kambariai\",\n      \"scheduleRoomHeader\": \"Suplanuoti kambarį\",\n      \"startRoom\": \"Pradėti kambarį\",\n      \"modal\": {\n        \"needsFuture\": \"Turi būti ateityje\",\n        \"roomName\": \"Kambario pavadinimas\",\n        \"minLength\": \"Mažiausias ilgis 2\",\n        \"roomDescription\": \"Kambario aprašymas\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Pokalbis\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"Buvai užblokuotas šiame kambaryje\",\n      \"waitAlert\": \"Prieš išsiųsdami kitą pranešimą, turite palaukti sekundę\",\n      \"search\": \"Ieškoti\",\n      \"searchResults\": \"Paieškos rezultatai\",\n      \"recent\": \"Dažniausiai naudojami\",\n      \"sendMessage\": \"Siųsti žinutę\",\n      \"whisper\": \"Pašnibšdėti\",\n      \"welcomeMessage\": \"Sveikas atvykęs į kambarį\",\n      \"roomDescription\": \"Kambario aprašymas\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/lv/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Ielādēt vairāk\",\n    \"loading\": \"Ielāde...\",\n    \"noUsersFound\": \"Lietotāji nav atrasti\",\n    \"ok\": \"labi\",\n    \"yes\": \"jā\",\n    \"no\": \"nē\",\n    \"cancel\": \"atcelt\",\n    \"save\": \"saglabāt\",\n    \"edit\": \"rediģēt\",\n    \"delete\": \"dzēst\",\n    \"joinRoom\": \"Pieslēgties istabai\",\n    \"copyLink\": \"Kopēt saiti\",\n    \"copied\": \"Nokopēts\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Skaņa izslēgta | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Izstrādes vēsture\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Ziņot par kļūdu\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"bloķēšana\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lietotāju saraksts, kuri ir publiskā istabā un kuriem Jūs sekojat.\",\n      \"currentRoom\": \"Pašlaik istabā:\",\n      \"startPrivateRoom\": \"Izveidot privātu istabu ar viņiem\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Izveidot istabu\",\n      \"refresh\": \"Atjaunot\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Istaba pazuda, atgriezieties atpakaļ\",\n      \"shareRoomLink\": \"Dalīties ar saiti uz istabu\",\n      \"inviteFollowers\": \"Jūs varat uzaicināt savus sekotājus, kuri pašlaik ir tiešsaistē :\",\n      \"whenFollowersOnline\": \"Kad jūsu sekotāji ir tiešsaistē, viņi tiek parādīti šeit.\"\n    },\n    \"login\": {\n      \"headerText\": \"Paceļam balss sarunas līdz mēnesim 🚀\",\n      \"featureText_1\": \"Tumšais motīvs\",\n      \"featureText_2\": \"Atvērtā reģistrācija\",\n      \"featureText_3\": \"Starpplatformu\",\n      \"featureText_4\": \"Atvērtais pirmkods\",\n      \"featureText_5\": \"Teksta čats\",\n      \"featureText_6\": \"Darbojas ar Doge\",\n      \"loginGithub\": \"Ieiet caur GitHub\",\n      \"loginTwitter\": \"Ieiet caur Twitter\",\n      \"createTestUser\": \"Izveidot izmēģinājuma lietotāju\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Iziet\",\n      \"probablyLoading\": \"visticamāk, notiek ielāde...\",\n      \"voiceSettings\": \"salss ziņu iestatījumi\",\n      \"soundSettings\": \"skaņas iestatījumi\",\n      \"deleteAccount\": \"dzēst kontu\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Šī lapa tika pazaudēta sarunā.\",\n      \"goHomeMessage\": \"Nepārdzīvojiet. Jūs varat\",\n      \"goHomeLinkText\": \"doties mājās\"\n    },\n    \"room\": {\n      \"speakers\": \"Runātāji\",\n      \"requestingToSpeak\": \"Vēlas runāt\",\n      \"listeners\": \"Klausītāji\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"meklēšana...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"skaņas\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"rediģēt profilu\",\n      \"followsYou\": \"seko jums\",\n      \"followers\": \"sekotāji\",\n      \"following\": \"seko\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Mikrofona iestatījumi\",\n      \"mic\": \"mikrofons:\",\n      \"permissionError\": \"mikrofons nav atrasts, tas nav pieslēgts vai pārlūkam nav atbilstošās atļaujas.\",\n      \"refresh\": \"atjaunināt mikrofonu sarakstu\",\n      \"volume\": \"skaļums:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bloķētie lietotāji\",\n      \"unban\": \"atbloķēt\",\n      \"noBans\": \"nav bloķētu lietotāju\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Pamest istabu\",\n      \"confirmLeaveRoom\": \"Vai esat pārliecināts?\",\n      \"leave\": \"Iziet\",\n      \"inviteUsersToRoomBtn\": \"Uzaicināt lietotājus uz istabu\",\n      \"invite\": \"Uzaicināt\",\n      \"toggleMuteMicBtn\": \"Ieslēgt/izslēgt mikrofonu\",\n      \"mute\": \"Izslēgt skaņu\",\n      \"unmute\": \"Ieslēgt skaņu\",\n      \"makeRoomPublicBtn\": \"Atvērt istabu!\",\n      \"settings\": \"Iestatījumi\",\n      \"speaker\": \"Runātājs\",\n      \"listener\": \"Klausītāji\",\n      \"chat\": \"Čats\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Jūsu ierīce nav atbalstīta. Jūs varat izveidot\",\n      \"linkText\": \"pieprasījumu caur GitHub\",\n      \"addSupport\": \"un es centīšos pievienot atbalstu jūsu ierīcei.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"uzaicināts\",\n      \"inviteToRoom\": \"uzaicināt uz istabu\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Nevar piekļūt jūsu mikrofonam (iespējams, jums vajag pārbaudīt pārluka iestatījumus, vai atsvaidzināt lapu)\",\n      \"dismiss\": \"noraidīt\",\n      \"tryAgain\": \"mēģināt vēlreiz\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"uzlikt aktivācijas taustiņu\",\n      \"listening\": \"klausās\",\n      \"toggleMuteKeybind\": \"Skaņas izslēgšanas taustiņš\",\n      \"togglePushToTalkKeybind\": \":Īslaicīgās mikrofona ieslēgšanas taustiņš\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"nezināmu iemeslu dēļ, nav pieejama audiosaziņa\"\n    },\n    \"addToCalendar\": { \"add\": \"Pievienot kalendārā\" },\n    \"wsKilled\": {\n      \"description\": \"Serveris iznīcināja WebSocket. Parasti tas notiek, kad jūs atverat mājaslapu citā logā.\",\n      \"reconnect\": \"atkārtoti pievienoties\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"publiska\",\n        \"private\": \"privāta\",\n        \"roomName\": \"istabas nosaukums\",\n        \"roomDescription\": \"istabas apraksts\",\n        \"descriptionError\": \"maksimālais garums 500\",\n        \"nameError\": \"garumam jābūt no 2 līdz 60 simboliem\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Izveidota jauna istaba\",\n        \"roomInviteFrom\": \"Uzaicinājums uz istabu no\",\n        \"justStarted\": \"Viņi tikko sāka\",\n        \"likeToJoin\": \", vēlaties pievienoties?\",\n        \"inviteReceived\": \"jūs tikāt uzaicināti uz\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"lietotājvārds aizņemts\",\n        \"avatarUrlError\": \"Nederīgs attēls\",\n        \"avatarUrlLabel\": \"Attēli URL no Github/Twitter/Discord\",\n        \"displayNameError\": \"garums no 2 līdz 50 simboliem\",\n        \"displayNameLabel\": \"Publiskais vārds\",\n        \"usernameError\": \"garums no 4 līdz 15 simboliem, tikai burtciparu simboli un pasvītra\",\n        \"usernameLabel\": \"Lietotājvārds\",\n        \"bioError\": \"maksimālais garums 160 simboli\",\n        \"bioLabel\": \"Par mani\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Vai esat pārliecināti, ka vēlaties nobloķēt šo lietotāju no visām jūsu telpām?\",\n        \"blockUser\": \"nobloķēt lietotāju\",\n        \"makeMod\": \"iestatīt par moderatoru\",\n        \"unmod\": \"noņemt moderātoru\",\n        \"addAsSpeaker\": \"pievienot runātāju\",\n        \"moveToListener\": \"pārvietot uz klausītājiem\",\n        \"banFromChat\": \"bloķet no čata\",\n        \"banFromRoom\": \"bloķēt no šis istabas\",\n        \"goBackToListener\": \"kļūt par klausītāju\",\n        \"deleteMessage\": \"izdzēst šo ziņu\",\n        \"makeRoomCreator\": \"iestatīt par šis istabas administrātoru\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"pieprasīt runāšanas atļauju\",\n        \"makePublic\": \"padarīt istabu publisku\",\n        \"makePrivate\": \"padarīt istabu privātu\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Ieplānotās istabas\",\n      \"noneFound\": \"nav atrastas\",\n      \"allRooms\": \"visas ieplānotās istabas\",\n      \"myRooms\": \"manas ieplānotās istabas\",\n      \"scheduleRoomHeader\": \"Ieplānot istabu\",\n      \"startRoom\": \"izveidot istabu\",\n      \"modal\": {\n        \"needsFuture\": \"vajag būt nākotnē\",\n        \"roomName\": \"istabas nosaukums\",\n        \"minLength\": \"minimālais garums 2\",\n        \"roomDescription\": \"apraksts\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"čats\",\n      \"emotesSoon\": \"[emocijas drīz]\",\n      \"bannedAlert\": \"Jūs tikāt nobloķēti čatā\",\n      \"waitAlert\": \"Ir jauzgaida sekunde, pirms nosūtīt nākamo ziņu\",\n      \"search\": \"Meklēšana\",\n      \"searchResults\": \"Meklēšanas rezultāti\",\n      \"recent\": \"Bieži izmantotie \",\n      \"sendMessage\": \"Nosūtīt ziņu\",\n      \"whisper\": \"Čukstēt\",\n      \"welcomeMessage\": \"Laipni lūgti čatā!\",\n      \"roomDescription\": \"istabas apraksts\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Raķetes uzpilde\",\n      \"takingOff\": \"Palaišana\",\n      \"inSpace\": \"Esam gaisā\",\n      \"approachingMoon\": \"Tuvojas mēnesim\",\n      \"lunarDoge\": \"Mēness doge\",\n      \"approachingSun\": \"Tuvojas saulei\",\n      \"solarDoge\": \"Saulainais doge\",\n      \"approachingGalaxy\": \"Tuvojas galaktikai\",\n      \"galacticDoge\": \"Galaktiskais doge\",\n      \"spottedLife\": \"Pamanīta planēta ar dzīvību radību\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/nb/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Last inn flere\",\n    \"loading\": \"Laster inn...\",\n    \"noUsersFound\": \"Ingen brukere funnet\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Ja\",\n    \"no\": \"Nei\",\n    \"cancel\": \"Avbryt\",\n    \"save\": \"Lagre\",\n    \"edit\": \"Rediger\",\n    \"delete\": \"Slett\",\n    \"joinRoom\": \"Bli med i rommet\",\n    \"copyLink\": \"Kopiér lenke\",\n    \"copied\": \"Kopiert\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Vær oppmerksom på at bruk av DogeHouse uten å gi nettleseren nødvendige tillatelser kan føre til uønskede feil\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Dempet | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Opprinnelse\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Rapporter en feil\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"utesteng\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Liste over brukere som ikke er i et privat rom, og som du følger.\",\n      \"currentRoom\": \"For tiden i:\",\n      \"startPrivateRoom\": \"Start et privat rom med dem\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"følg\",\n      \"followingHim\": \"følger\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Lag et rom\",\n      \"refresh\": \"Last inn på nytt\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Rommet finnes ikke lengre, gå tilbake\",\n      \"shareRoomLink\": \"Del lenken til rommet\",\n      \"inviteFollowers\": \"Du kan invitere følgerne dine som er pålogget:\",\n      \"whenFollowersOnline\": \"Når følgerne dine er pålogget, vil de dukke opp her.\"\n    },\n    \"login\": {\n      \"headerText\": \"Tar talesamtaler til månen 🚀\",\n      \"featureText_1\": \"Mørkt tema\",\n      \"featureText_2\": \"Åpne registreringer\",\n      \"featureText_3\": \"Støtte på tvers av plattformer\",\n      \"featureText_4\": \"Åpene kildekoden\",\n      \"featureText_5\": \"Tekstchat\",\n      \"featureText_6\": \"Drevet av Doge\",\n      \"loginGithub\": \"Logg inn med GitHub\",\n      \"loginTwitter\": \"Logg inn med Twitter\",\n      \"createTestUser\": \"Opprett en test-bruker\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Logg ut\",\n      \"probablyLoading\": \"Lastes sikkert inn...\",\n      \"voiceSettings\": \"Gå til taleinnstillinger\",\n      \"soundSettings\": \"Gå til lydinnstillinger\",\n      \"deleteAccount\": \"Slett konto\",\n      \"overlaySettings\": \"Gå til overleggsinnstillinger\",\n      \"couldNotFindUser\": \"Beklager, vi kunne ikke finne denne brukeren..\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oi sann! Denne siden ble tapt i samtalen.\",\n      \"goHomeMessage\": \"Ikke vær redd. Du kan\",\n      \"goHomeLinkText\": \"gå hjem\"\n    },\n    \"room\": {\n      \"speakers\": \"Talere\",\n      \"requestingToSpeak\": \"Ber om å få snakke\",\n      \"listeners\": \"Lyttere\",\n      \"allowAll\": \"Tillat alle\",\n      \"allowAllConfirm\": \"Er du sikker? Dette vil tillate alle {{count}} spørrende brukere til å snakke\"\n    },\n    \"searchUser\": { \"search\": \"søk...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Lyder\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Rediger profil\",\n      \"followsYou\": \"Følger deg\",\n      \"followers\": \"følgere\",\n      \"following\": \"følger\",\n      \"followHim\": \"Følg\",\n      \"followingHim\": \"Følger\",\n      \"copyProfileUrl\": \"Kopiér profil URL\",\n      \"urlCopied\": \"URL kopiert til utklippstavlen\",\n      \"unfollow\": \"Slutt å følge\",\n      \"about\": \"Om bruker\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Taleinnstillinger\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Ingen mikrofoner er funnet, du har enten ingen enheter tilgjenngelig, eller så har du ikke gitt tillatelse til å bruke den.\",\n      \"refresh\": \"Oppdater mikrofon-listen\",\n      \"volume\": \"Volum:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Ugyldig tittel\", \"label\": \"Angi tittel\" },\n      \"header\": \"Innstillinger for overlegg\"\n    },\n    \"download\": {\n      \"starting\": \"Starter nedlastning...\",\n      \"failed\": \"Kunne ikke gjøre automatisk nedlastning, prøv igjen senere\",\n      \"visit_gh\": \"Besøk Github lanseringer\",\n      \"prompt\": \"Klikk på knappen nedenfor, for å starte nedlastningen\",\n      \"download_now\": \"Last ned nå\",\n      \"download_for\": \"Nedlastning for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Utestengte brukere\",\n      \"unban\": \"Opphev utestengelsen\",\n      \"noBans\": \"Ingen har blitt utestengt ennå\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Forlat dette rommet\",\n      \"confirmLeaveRoom\": \"Er du sikker på at du vil forlate rommet?\",\n      \"leave\": \"Forlat\",\n      \"inviteUsersToRoomBtn\": \"Inviter brukere til rommet\",\n      \"invite\": \"Inviter\",\n      \"toggleMuteMicBtn\": \"Veksle demping av mikrofon\",\n      \"mute\": \"Demp\",\n      \"unmute\": \"Opphev demping\",\n      \"makeRoomPublicBtn\": \"Gjør rommet offentlig!\",\n      \"settings\": \"Innstillinger\",\n      \"speaker\": \"Taler\",\n      \"listener\": \"Lytter\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Enheten din støttes foreløpig ikke. Du kan opprette ett\",\n      \"linkText\": \"problem på GitHub\",\n      \"addSupport\": \"så skal jeg prøve å legge til støtte for enheten din.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invitert\",\n      \"inviteToRoom\": \"Inviter til rom\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Tillatelse nektet når vi prøvde å få tilgang til mikrofonen din (du må kanskje gå til nettleserinnstillingene og laste inn siden på nytt)\",\n      \"dismiss\": \"Avvis\",\n      \"tryAgain\": \"Prøv igjen\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Angi hurtigtast\",\n      \"listening\": \"Lytter\",\n      \"toggleMuteKeybind\": \"Veksle demping av hurtigtast\",\n      \"togglePushToTalkKeybind\": \"Veksle trykk for å snakke hurtigtast\",\n      \"toggleOverlayKeybind\": \"Overlay hurtigtast\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Ingen lydmottaker av en eller annen grunn\"\n    },\n    \"addToCalendar\": { \"add\": \"Legg til i Kalender\" },\n    \"wsKilled\": {\n      \"description\": \"Tilkoblingen din ble avsluttet av serveren. Dette skjer vanligvis når du åpner nettsiden i en annen fane.\",\n      \"reconnect\": \"Koble til igjen\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Offentlig\",\n        \"private\": \"Privat\",\n        \"roomName\": \"Romnavn\",\n        \"roomDescription\": \"Rombeskrivelse\",\n        \"descriptionError\": \"Maksimal lengde er 500 tegn\",\n        \"nameError\": \"Må være mellom 2 og 60 tegn\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nytt rom er opprettet\",\n        \"roomInviteFrom\": \"Rominvitasjon fra\",\n        \"justStarted\": \"De startet nettopp\",\n        \"likeToJoin\": \", vil du bli med?\",\n        \"inviteReceived\": \"du har blitt invitert til\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Brukernavnet er allerede i bruk\",\n        \"avatarUrlError\": \"Ugyldig bilde\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord profilbilde URL\",\n        \"displayNameError\": \"Lengden må være mellom 2 og 50 tegn\",\n        \"displayNameLabel\": \"Visningsnavn\",\n        \"usernameError\": \"Lengden må være mellom 4 og 15 tegn og kan kun inneholde alfanumerisk/understrek\",\n        \"usernameLabel\": \"Brukernavn\",\n        \"bioError\": \"Maksimal lengde er 160 tegn\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Er du sikker på at du vil blokkere brukeren fra å bli med i alle nye rom du lager?\",\n        \"blockUser\": \"Blokkér bruker\",\n        \"makeMod\": \"Gjør til moderator\",\n        \"unmod\": \"Fjern som moderator\",\n        \"addAsSpeaker\": \"Legg til som taler\",\n        \"moveToListener\": \"Flytt til lytter\",\n        \"banFromChat\": \"Utesteng fra chatten\",\n        \"banFromRoom\": \"Utesteng fra rommet\",\n        \"goBackToListener\": \"Gå tilbake til lytter\",\n        \"deleteMessage\": \"Slett denne meldingen\",\n        \"makeRoomCreator\": \"Opprett rom-administrator\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Krev tillatelse for å snakke\",\n        \"makePublic\": \"Gjør rommet offentlig\",\n        \"makePrivate\": \"Gjør rommet privat\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Mennesker\",\n      \"online\": \"Pålogget\",\n      \"noOnline\": \"Du har ingen venner pålogget\",\n      \"showMore\": \"Vis flere\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Kommende rom\",\n      \"exploreMoreRooms\": \"Utforsk flere rom\"\n    },\n    \"search\": {\n      \"placeholder\": \"Søk etter rom, brukere eller kategorier\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Språk\",\n      \"reportABug\": \"Rapporter en feil\",\n      \"useOldVersion\": \"Bruk den gamle versjonen\",\n      \"logOut\": {\n        \"button\": \"Logg ut\",\n        \"modalSubtitle\": \"Er du sikker på at du vil logge ut?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Planlagte rom\",\n      \"noneFound\": \"Ingen funnet\",\n      \"allRooms\": \"Alle planlagte rom\",\n      \"myRooms\": \"Mine planlagte rom\",\n      \"scheduleRoomHeader\": \"Planlegg et rom\",\n      \"startRoom\": \"Opprett et rom\",\n      \"modal\": {\n        \"needsFuture\": \"Må være i fremtiden\",\n        \"roomName\": \"Romnavn\",\n        \"minLength\": \"Minimum lengde er 2 tegn\",\n        \"roomDescription\": \"Beskrivelse\"\n      },\n      \"tommorow\": \"I MORGEN\",\n      \"today\": \"I DAG\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Er du sikker på at du vil slette det planlagte rommet?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes snart]\",\n      \"bannedAlert\": \"Du ble utestengt fra chatten\",\n      \"waitAlert\": \"Du må vente ett sekund før du kan sende en ny melding\",\n      \"search\": \"Søk\",\n      \"searchResults\": \"Søkeresultater\",\n      \"recent\": \"Ofte brukt\",\n      \"sendMessage\": \"Send en melding\",\n      \"whisper\": \"Hviske\",\n      \"welcomeMessage\": \"Velkommen til chatten!\",\n      \"roomDescription\": \"Rombeskrivelse\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fyller på rakkett med drivstoff\",\n      \"takingOff\": \"Tar av\",\n      \"inSpace\": \"I verdensrommet\",\n      \"approachingMoon\": \"Nærmer seg månen\",\n      \"lunarDoge\": \"Måne-doge\",\n      \"approachingSun\": \"Nærmer seg solen\",\n      \"solarDoge\": \"Sol-doge\",\n      \"approachingGalaxy\": \"Nærmer seg galaksen\",\n      \"galacticDoge\": \"Galakse-doge\",\n      \"spottedLife\": \"Planet med liv oppdaget\"\n    },\n    \"feed\": { \"yourFeed\": \"Din Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ne/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"थप लोड गर्नुहोस्\",\n    \"loading\": \"लोड हुदैछ ...\",\n    \"noUsersFound\": \"कुनै प्रयोगकर्ता भेटिएन\",\n    \"ok\": \"ठिक छ\",\n    \"yes\": \"हो\",\n    \"no\": \"होईन\",\n    \"cancel\": \"रद्द गर्नुहोस्\",\n    \"save\": \"save\",\n    \"edit\": \"सम्पादन गर्नुहोस्\",\n    \"delete\": \"मेटाउन\",\n    \"joinRoom\": \"कोठामा सामेल हुनुहोस्\",\n    \"copyLink\": \"लिंक प्रतिलिपि गर्नुहोस्\",\n    \"copied\": \"copied\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"हाम्रो कथा\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"गल्ती रिपोर्ट गर्नुहोस्\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"प्रतिबन्ध\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"प्रयोगकर्ताहरूको सूची जो प्राइवेट रूम छैनन् र तपाइँ फॉलो गर्नुहुन्छा।\",\n      \"currentRoom\": \"अहिले हुनुहुन्छ:\",\n      \"startPrivateRoom\": \"यीनीहरू संग एक प्राइवेट रूम शुरू गर्नुहोस्\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"रूम बनाउनुहोस\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"रूम गयो, पछाडि जानुहोस्\",\n      \"shareRoomLink\": \"रूमको लिंक शेयर गर्नुहोस्\",\n      \"inviteFollowers\": \"तपाईं आफ्नो अनलाइन फॉलोअर्स आमन्त्रित पठाउन सक्नुहुनेछ:\",\n      \"whenFollowersOnline\": \"जब तपाइँका अनुयायीहरू अनलाइन हुन्छन, तिनीहरू यहाँ देखा पर्नेछन।\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"log in with GitHub\",\n      \"loginTwitter\": \"log in with Twitter\",\n      \"createTestUser\": \"create test user\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"logout\",\n      \"probablyLoading\": \"सायद लोड हुँदैछ ...\",\n      \"voiceSettings\": \"वॉइस सेटिंग्समा जानुहोस्\",\n      \"soundSettings\": \"साउंड सेटिंग्समा जानुहोस्\",\n      \"deleteAccount\": \"खाता मेटाउनुहोस्\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"ओहो ! यो पृष्ठ त कुराकानीमै हरायो।.\",\n      \"goHomeMessage\": \"चिन्ता नलिनुहोस्। तपाईं गर्न सक्नुहुन्छ। \",\n      \"goHomeLinkText\": \"मुख्य पृष्ठमा जानुहोस\"\n    },\n    \"room\": {\n      \"speakers\": \"स्पिकरहरू\",\n      \"requestingToSpeak\": \"बोल्न अनुरोध गर्दै\",\n      \"listeners\": \"श्रोताहरु\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"खोजी गर्नुहोस्...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"साउंड\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"प्रोफाइल सम्पादन गर्नुहोस\",\n      \"followsYou\": \"follows you\",\n      \"followers\": \"फॉलोअर्स\",\n      \"following\": \"फॉलोइंग\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"वॉइस सेटिंग्स\",\n      \"mic\": \"माइक\",\n      \"permissionError\": \"कुनै माईक फेला परेन, तपाईंले या त यस वेबसाइटलाई अनुमति दिनुभएन वा माइकमा प्लग इन गर्न बिर्सनुभयो।\",\n      \"refresh\": \"रिफ्रेश माइक लिस्ट\",\n      \"volume\": \"भोल्युम:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"प्रतिबन्धित प्रयोगकर्ताहरू\",\n      \"unban\": \"unban\",\n      \"noBans\": \"कसैलाई प्रतिबन्ध लगाइएको छैन\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"हाल भएको रूम छोड्नुहोस\",\n      \"confirmLeaveRoom\": \"के तपाई साँच्चै छोड्न चाहानुहुन्छ? \",\n      \"leave\": \"छोड्नुहोस\",\n      \"inviteUsersToRoomBtn\": \"रूममा प्रयोगकर्ताहरूलाई आमन्त्रित गर्नुहोस्\",\n      \"invite\": \"आमंत्रण\",\n      \"toggleMuteMicBtn\": \"म्यूट माइक्रोफोन टॉगल गर्नुहोस\",\n      \"mute\": \"म्यूट गर्नुहोस्\",\n      \"unmute\": \"अनम्यूट गर्नुहोस्\",\n      \"makeRoomPublicBtn\": \"रूम सार्वजनिक बनाउनुहोस!\",\n      \"settings\": \"सेटिंग्स\",\n      \"speaker\": \"स्पिकर\",\n      \"listener\": \"श्रोता\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"तपाईंको उपकरण हाल समर्थित छैन। तपाईं \",\n      \"linkText\": \"GitHub issue\",\n      \"addSupport\": \"बनाउन सक्नुहुन्छ र म तपाईंको उपकरणको लागि समर्थन थप्न प्रयास गर्दछु।\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"आमन्त्रित\",\n      \"inviteToRoom\": \"रूममा आमन्त्रित गर्नुहोस्\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"तपाइँको माइक पहुँच गर्न अनुमति अस्वीकृत गरिएको छ((तपाइँ ब्राउजर सेटिंग्समा गएर र पृष्ठ पुन: लोड गर्न आवश्यक पर्दछ))\",\n      \"dismiss\": \"खारेज गर्नुहोस\",\n      \"tryAgain\": \"फेरि प्रयास गर्नुहोस्\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"कीबाइंड सेट गर्नुहोस्\",\n      \"listening\": \"सुन्दै\",\n      \"toggleMuteKeybind\": \"टॉगल म्यूट कीबाइंड\",\n      \"togglePushToTalkKeybind\": \"टॉगल push-to-talk कीबाइंड\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"कुनै कारणका लागि कुनै अडियो उपभोक्ता छैन\"\n    },\n    \"addToCalendar\": { \"add\": \"क्यालेन्डरमा थप्नुहोस\" },\n    \"wsKilled\": {\n      \"description\": \"Websocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"पुनः जडान गर्नुहोस्\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"सार्वजनिक\",\n        \"private\": \"प्राइवेट\",\n        \"roomName\": \"रूमको नाम\",\n        \"roomDescription\": \"रूमको बारेमा\",\n        \"descriptionError\": \"अधिकतम लम्बाई 500 अक्षर\",\n        \"nameError\": \"२ देखि ६० अक्षरसम्म लामो हुनुपर्दछ\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"नयाँ रूम बनेको छ\",\n        \"roomInviteFrom\": \"रूम बाट आमन्त्रित\",\n        \"justStarted\": \"उनिहरूले भर्खर सुरू गरे\",\n        \"likeToJoin\": \", के तपाईं सामेल हुन चाहानुहुन्छ?\",\n        \"inviteReceived\": \"तपाईंलाई यहाँ आमन्त्रित गरिएको छ\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"length 2 to 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"max length of 160 characters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"के तपाई पक्का यस उपयोगकर्तालाई तपाईले बनाउनु भएको कुनै रूममा सामेल हुनबाट रोक्न चाहानुहुन्छ?\",\n        \"blockUser\": \"ब्लक प्रयोगकर्ता\",\n        \"makeMod\": \"make mod\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"स्पिकरको रूपमा थप्नुहोस्\",\n        \"moveToListener\": \"श्रोताको रूपमा सार्नुहोस्\",\n        \"banFromChat\": \"ban from chat\",\n        \"banFromRoom\": \"रूमबाट प्रतिबन्ध\",\n        \"goBackToListener\": \"श्रोताको लागि पछाडि जानुहोस\",\n        \"deleteMessage\": \"यो मेसेज मेटाउनुहोस\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"बोल्नको लागि अनुमति चाहिन्छ\",\n        \"makePublic\": \"रूम सार्वजनिक बनाउनुहोस\",\n        \"makePrivate\": \"रूम प्राइवेट बनाउनुहोस\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"निर्धारित रूम\",\n      \"noneFound\": \"कुनै फेला परेन\",\n      \"allRooms\": \"सबै अनुसूचित रूम\",\n      \"myRooms\": \"मेरो अनुसूचित रूम\",\n      \"scheduleRoomHeader\": \"अनुसूचित रूम\",\n      \"startRoom\": \"नयाँ रूम\",\n      \"modal\": {\n        \"needsFuture\": \"भविष्यमा हुनुपर्छ\",\n        \"roomName\": \"रूमको नाम\",\n        \"minLength\": \"न्यूनतम लम्बाई २\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"तपाइँ च्याट गर्न प्रतिबन्धित गर्नुभयो\",\n      \"waitAlert\": \"तपाईंले अर्को मेसेज पठाउनु अघि एक सेकेन्ड पर्खनु पर्छ\",\n      \"search\": \"खोजी गर्नुहोस्\",\n      \"searchResults\": \"खोजी परिणामहरू\",\n      \"recent\": \"प्राय: प्रयोग हुने\",\n      \"sendMessage\": \"एक मेसेज पठाउनुहोस्\",\n      \"whisper\": \"कानेखुसि\",\n      \"welcomeMessage\": \"च्याटमा स्वागत छ!\",\n      \"roomDescription\": \"रुमको बारेमा\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/nl/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Meer laden\",\n    \"loading\": \"Aan het laden...\",\n    \"noUsersFound\": \"Geen gebruikers gevonden\",\n    \"ok\": \"Oké\",\n    \"yes\": \"Ja\",\n    \"no\": \"Nee\",\n    \"cancel\": \"Annuleren\",\n    \"save\": \"Opslaan\",\n    \"edit\": \"Bewerken\",\n    \"delete\": \"Verwijderen\",\n    \"joinRoom\": \"Kamer betreden\",\n    \"copyLink\": \"Kopieer link\",\n    \"copied\": \"Gekopieerd\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Houd er rekening mee dat het uitvoeren van DogeHouse zonder de benodigde permissies ongewenste fouten kan veroorzaken\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Gedempt | DogeHouse\",\n    \"deafenedTitle\": \"Niet hoorbaar | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Verbonden\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Ontstaan\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Probleem melden\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Verban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lijst van gebruikers die je volgt en niet in een privékamer zijn.\",\n      \"currentRoom\": \"Momenteel in:\",\n      \"startPrivateRoom\": \"Start een privékamer met deze persoon\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Volg\",\n      \"followingHim\": \"Volgend\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Kamer maken\",\n      \"refresh\": \"Verversen\",\n      \"editRoom\": \"Kamer aanpassen\",\n      \"desktopAlert\": \"Download de DogeHouse Bureaublad-app vandaag!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Kamer bestaat niet, ga terug\",\n      \"shareRoomLink\": \"Link van deze kamer delen\",\n      \"inviteFollowers\": \"Je kunt je volgers die online zijn uitnodigen:\",\n      \"whenFollowersOnline\": \"Als je volgers online zijn zullen ze hier verschijnen.\"\n    },\n    \"login\": {\n      \"headerText\": \"Wij brengen gesprekken naar de maan! 🚀\",\n      \"featureText_1\": \"Donkere modus\",\n      \"featureText_2\": \"Open registraties\",\n      \"featureText_3\": \"Platformoverschrijdend\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Tekstchat\",\n      \"featureText_6\": \"Mogelijk gemaakt door Doge\",\n      \"loginGithub\": \"Log in met GitHub\",\n      \"loginTwitter\": \"Log in met Twitter\",\n      \"createTestUser\": \"Maak een testgebruiker\",\n      \"loginDiscord\": \"Log in met Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Afmelden\",\n      \"probablyLoading\": \"Waarschijnlijk aan het laden...\",\n      \"voiceSettings\": \"Ga naar spraakinstellingen\",\n      \"soundSettings\": \"Ga naar geluidsinstellingen\",\n      \"deleteAccount\": \"Verwijder account\",\n      \"overlaySettings\": \"Ga naar overlay-instellingen\",\n      \"couldNotFindUser\": \"Sorry, die gebruiker kon niet gevonden worden\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Oeps! Deze pagina is verloren gegaan tijdens het gesprek.\",\n      \"goHomeMessage\": \"Maak je geen zorgen. Je kan\",\n      \"goHomeLinkText\": \"terug naar de homepagina\"\n    },\n    \"room\": {\n      \"speakers\": \"Sprekers\",\n      \"requestingToSpeak\": \"Aanvragen om te spreken\",\n      \"listeners\": \"Luisteraars\",\n      \"allowAll\": \"Iedereen toestaan\",\n      \"allowAllConfirm\": \"Weet je het zeker? Dit geeft toestemming aan {{count}} verzoekende gebruikers om te spreken\"\n    },\n    \"searchUser\": { \"search\": \"Zoeken...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Geluiden\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profiel bewerken\",\n      \"followsYou\": \"Volgt jou\",\n      \"followers\": \"Volgers\",\n      \"following\": \"Volgend\",\n      \"followHim\": \"Volg\",\n      \"followingHim\": \"Volgend\",\n      \"copyProfileUrl\": \"kopieer link naar profiel\",\n      \"urlCopied\": \"Link gekopieerd naar klembord\",\n      \"unfollow\": \"Ontvolgen\",\n      \"about\": \"Over\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Spraakinstellingen\",\n      \"mic\": \"Microfoon:\",\n      \"permissionError\": \"Geen microfoon gevonden, waarschijnlijk is er geen microfoon aangesloten of heb je geen toestemming gegeven.\",\n      \"refresh\": \"Microfoonlijst verversen\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Ongeldige applicatietitel\",\n        \"label\": \"Voer applicatietitel in\"\n      },\n      \"header\": \"Overlay-instellingen\"\n    },\n    \"download\": {\n      \"starting\": \"Aan het downloaden...\",\n      \"failed\": \"De automatische download is mislukt, probeer het later nog een keer\",\n      \"visit_gh\": \"Bezoek Github Releases\",\n      \"prompt\": \"Klik op de onderstaande knop om te downloaden\",\n      \"download_now\": \"Nu downloaden\",\n      \"download_for\": \"Download voor %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Verbannen Gebruikers\",\n      \"unban\": \"Ontban\",\n      \"noBans\": \"Er is nog niemand verbannen\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Verlaat huidige kamer\",\n      \"confirmLeaveRoom\": \"Weet je zeker dat je weg wilt gaan?\",\n      \"leave\": \"Verlaat\",\n      \"inviteUsersToRoomBtn\": \"Nodig gebruikers uit voor deze kamer\",\n      \"invite\": \"Uitnodigen\",\n      \"toggleMuteMicBtn\": \"Microfoon aan- of uitzetten\",\n      \"mute\": \"Dempen\",\n      \"unmute\": \"Dempen opheffen\",\n      \"makeRoomPublicBtn\": \"Maak kamer openbaar!\",\n      \"settings\": \"Instellingen\",\n      \"speaker\": \"Spreker\",\n      \"listener\": \"Luisteraar\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Schakel hoorbaarheid\",\n      \"deafen\": \"Onhoorbaar maken\",\n      \"undeafen\": \"Hoorbaar maken\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Je apparaat wordt momenteel niet ondersteund. Je kunt een\",\n      \"linkText\": \"issue op GitHub\",\n      \"addSupport\": \"maken en we zullen ondersteuning voor je apparaat proberen toe te voegen.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Uitgenodigd\",\n      \"inviteToRoom\": \"Nodig uit voor kamer\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Geen microfoon gevonden, waarschijnlijk is er geen microfoon aangesloten of heb je geen toestemming gegeven.\",\n      \"dismiss\": \"Negeer\",\n      \"tryAgain\": \"Probeer opnieuw\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Stel sneltoets in\",\n      \"listening\": \"Aan het luisteren\",\n      \"toggleMuteKeybind\": \"Schakel demp sneltoets\",\n      \"togglePushToTalkKeybind\": \"Schakel push-to-talk sneltoets\",\n      \"toggleOverlayKeybind\": \"Schakel overlay sneltoets\",\n      \"toggleDeafKeybind\": \"Schakel hoorbaarheid sneltoets\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Geen audioapparaat (om een of andere reden)\"\n    },\n    \"addToCalendar\": { \"add\": \"Voeg toe aan agenda\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket is gestopt door de server. Dit gebeurt meestal als je de site open hebt staan in een ander tabblad.\",\n      \"reconnect\": \"Opnieuw verbinden\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Openbaar\",\n        \"private\": \"Privé\",\n        \"roomName\": \"Kamernaam\",\n        \"roomDescription\": \"Kameromschrijving\",\n        \"descriptionError\": \"Maximale lengte van 500 karakters\",\n        \"nameError\": \"Moet tussen de 2 en 60 karakters lang zijn\",\n        \"subtitle\": \"Vul de volgende velden in om een kamer te starten\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nieuwe kamer gemaakt\",\n        \"roomInviteFrom\": \"Kameruitnodiging van\",\n        \"justStarted\": \"Ze zijn zojuist begonnen\",\n        \"likeToJoin\": \", wil je meedoen?\",\n        \"inviteReceived\": \"Je bent uitgenodigd voor\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Gebruikersnaam bezet\",\n        \"avatarUrlError\": \"Ongeldige afbeelding\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"2 tot 50 karakters\",\n        \"displayNameLabel\": \"Weergavenaam\",\n        \"usernameError\": \"4 tot 15 karakters en alleen letters, cijfers en underscore\",\n        \"usernameLabel\": \"Gebruikersnaam\",\n        \"bioError\": \"Maximaal 160 karakters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Weet je zeker dat je deze gebruiker wilt blokkeren van al je toekomstige kamers?\",\n        \"blockUser\": \"Blokkeer gebruiker\",\n        \"makeMod\": \"Maak beheerder\",\n        \"unmod\": \"Verwijder beheerder\",\n        \"addAsSpeaker\": \"Voeg toe als spreker\",\n        \"moveToListener\": \"Verplaats naar luisteraar\",\n        \"banFromChat\": \"Verban uit de chat\",\n        \"banFromRoom\": \"Verban uit de kamer\",\n        \"goBackToListener\": \"Ga terug naar luisteraar\",\n        \"deleteMessage\": \"Verwijder dit bericht\",\n        \"makeRoomCreator\": \"kamerbeheerder maken\",\n        \"unBanFromChat\": \"Ontban van chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Toestemming nodig om te spreken\",\n        \"makePublic\": \"Maak kamer openbaar\",\n        \"makePrivate\": \"Maak kamer privé\",\n        \"renamePublic\": \"Stel openbare kamernaam in\",\n        \"renamePrivate\": \"Stel privékamernaam in\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Mensen\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Er zijn momenteel geen vrienden online\",\n      \"showMore\": \"Toon meer\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Geplande kamers\",\n      \"exploreMoreRooms\": \"Ontdek meer kamers\"\n    },\n    \"search\": {\n      \"placeholder\": \"Zoek naar kamers, gebruikers of categorieën\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profiel\",\n      \"language\": \"Taal\",\n      \"reportABug\": \"Rapporteer een bug\",\n      \"useOldVersion\": \"Gebruik de oude versie\",\n      \"logOut\": {\n        \"button\": \"Uitloggen\",\n        \"modalSubtitle\": \"Weet je zeker dat je wilt uitloggen?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Debugger stoppen\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Geplande kamers\",\n      \"noneFound\": \"Geen gevonden\",\n      \"allRooms\": \"Alle geplande kamers\",\n      \"myRooms\": \"Mijn geplande kamers\",\n      \"scheduleRoomHeader\": \"Kamer inplannen\",\n      \"startRoom\": \"Kamer starten\",\n      \"modal\": {\n        \"needsFuture\": \"Moet in de toekomst zijn\",\n        \"roomName\": \"Kamer naam\",\n        \"minLength\": \"Naam moet minimaal 2 karakters zijn\",\n        \"roomDescription\": \"Beschrijving\"\n      },\n      \"tommorow\": \"MORGEN\",\n      \"today\": \"VANDAAG\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Weet je zeker dat je deze geplande kamer wilt verwijderen?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[Emotes binnenkort]\",\n      \"bannedAlert\": \"Je bent uit deze chat verbannen\",\n      \"waitAlert\": \"Je moet een seconde wachten voordat je weer een bericht kan sturen.\",\n      \"search\": \"Zoeken\",\n      \"searchResults\": \"Zoekresultaten\",\n      \"recent\": \"Vaak Gebruikt\",\n      \"sendMessage\": \"Stuur een Bericht\",\n      \"whisper\": \"Fluister\",\n      \"welcomeMessage\": \"Welkom in de chat!\",\n      \"roomDescription\": \"Kamer omschrijving\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Raket aan het tanken\",\n      \"takingOff\": \"Aan het opstijgen\",\n      \"inSpace\": \"In de ruimte\",\n      \"approachingMoon\": \"Maan aan het benaderen\",\n      \"lunarDoge\": \"Maan-hond\",\n      \"approachingSun\": \"Zon aan het benaderen\",\n      \"solarDoge\": \"Zonne-hond\",\n      \"approachingGalaxy\": \"Heelal aan het benaderen\",\n      \"galacticDoge\": \"Galactische Doge\",\n      \"spottedLife\": \"Planeet met leven gespot\"\n    },\n    \"feed\": { \"yourFeed\": \"Je Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/pl/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Załaduj więcej\",\n    \"loading\": \"Ładowanie...\",\n    \"noUsersFound\": \"Nie znaleziono użytkowników!\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Tak\",\n    \"no\": \"Nie\",\n    \"cancel\": \"Anuluj\",\n    \"save\": \"Zapisz\",\n    \"edit\": \"Edytuj\",\n    \"delete\": \"Usuń\",\n    \"joinRoom\": \"Dołącz do pokoju\",\n    \"copyLink\": \"Skopiuj link\",\n    \"copied\": \"Skopiowano!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Korzystanie z DogeHouse bez uprawnień dostępu może powodować niechciane błędy\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"dashboard\": \"Tablica\",\n    \"connectionTaken\": \"Połączenie zostało podjęte\",\n    \"mutedTitle\": \"Wyciszony mikrofon | DogeHouse\",\n    \"deafenedTitle\": \"Wyciszony dźwięk | DogeHouse\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Historia pochodzenia\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Zgłoś błąd\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Zablokuj\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista użytkowników którzy nie są w prywatnym pokoju i których obserwujesz.\",\n      \"currentRoom\": \"Obecnie w:\",\n      \"startPrivateRoom\": \"Załóż z nim prywatny pokój\",\n      \"title\": \"Osoby\"\n    },\n    \"followList\": {\n      \"followHim\": \"Obserwuj\",\n      \"followingHim\": \"Obserwujący\",\n      \"title\": \"Osoby\",\n      \"followingNone\": \"Nie obserwuje nikogo\",\n      \"noFollowers\": \"Brak obserwujących\"\n    },\n    \"home\": {\n      \"createRoom\": \"Nowy pokój\",\n      \"editRoom\": \"Edytuj pokój\",\n      \"refresh\": \"Odśwież\",\n      \"desktopAlert\": \"Pobierz aplikację DogeHouse na komputer już dzisiaj!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Pokój usunięty, wróć\",\n      \"shareRoomLink\": \"Udostępnij link do pokoju\",\n      \"inviteFollowers\": \"Możesz zaprosić swoich obserwujących którzy są online:\",\n      \"whenFollowersOnline\": \"Kiedy twoi obserwujący są online, pokażą się oni tutaj.\"\n    },\n    \"login\": {\n      \"headerText\": \"Zabieramy rozmowy głosowe na księżyc 🚀\",\n      \"featureText_1\": \"Tryb ciemny\",\n      \"featureText_2\": \"Otwarte Zapisy\",\n      \"featureText_3\": \"Wsparcie międzyplatformowe\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Czat tekstowy\",\n      \"featureText_6\": \"Zasilany przez Doge\",\n      \"loginGithub\": \"Zaloguj się przy użyciu GitHub\",\n      \"loginTwitter\": \"Zaloguj się przy użyciu Twitter\",\n      \"loginDiscord\": \"Zaloguj się przy użyciu Discord\",\n      \"createTestUser\": \"Stwórz testowego użytkownika\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Wyloguj się\",\n      \"probablyLoading\": \"prawdobodobnie ładowanie...\",\n      \"voiceSettings\": \"Ustawienia głosu\",\n      \"overlaySettings\": \"Ustawienia nakładki\",\n      \"soundSettings\": \"Ustawienia dźwięku\",\n      \"deleteAccount\": \"Usuń konto\",\n      \"couldNotFindUser\": \"Przepraszamy, nie udało się znaleźć tego użytkownika\",\n      \"privacySettings\": \"Ustawienia prywatności\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Ta strona zagubiła się w konwersacji.\",\n      \"goHomeMessage\": \"Nie martw się. Możesz\",\n      \"goHomeLinkText\": \"przejść na stronę domową\"\n    },\n    \"room\": {\n      \"speakers\": \"Mówiący\",\n      \"requestingToSpeak\": \"Prośba o pozwolenie na mówienie\",\n      \"listeners\": \"Słuchacze\",\n      \"allowAll\": \"Dopuść wszystkich\",\n      \"allowAllConfirm\": \"Czy na pewno? To dopuści {{count}} użytkowników do mówienia\"\n    },\n    \"searchUser\": { \"search\": \"szukaj...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Dźwięk\",\n      \"title\": \"Ustawienia dźwięku\",\n      \"playSound\": \"Odtwórz dźwięk\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Edytuj profil\",\n      \"followsYou\": \"Obserwuje Cię\",\n      \"followers\": \"Obserwujący\",\n      \"following\": \"Obserwujesz\",\n      \"followHim\": \"Obserwuj\",\n      \"unfollow\": \"Przestań obserwować\",\n      \"followingHim\": \"Obserwujesz\",\n      \"copyProfileUrl\": \"Skopiuj URL profilu\",\n      \"urlCopied\": \"URL zostało skopiowane do schowka\",\n      \"about\": \"O profilu\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"O profilu\",\n        \"rooms\": \"Pokoje\",\n        \"scheduled\": \"Zaplanowane\",\n        \"recorded\": \"Nagrane\",\n        \"clips\": \"Klipy\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Zablokuj\",\n      \"unblock\": \"Odblokuj\",\n      \"sendDM\": \"Wyślij prywatną wiadomość\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"Ten użytkownik cię zablokował!\",\n        \"default\": \"Ups! Nie udało się załadować profilu użytkownika.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ustawienia Głosu\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Nie znaleziono mikrofonów. Sprawdź podłączenie oraz pozwolenia tej stronie.\",\n      \"refresh\": \"Odśwież listę mikrofonów\",\n      \"volume\": \"Głośność:\",\n      \"title\": \"Ustawienia głosu\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Ustawienia nakładki\",\n      \"input\": {\n        \"errorMsg\": \"Wpisz poprawną nazwę aplikacji\",\n        \"label\": \"Nazwa aplikacji\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Rozpoczynanie pobierania...\",\n      \"failed\": \"Nie można było rozpocząć automatycznego pobierania, spróbuj ponownie później\",\n      \"visit_gh\": \"Odwiedź Github Releases\",\n      \"prompt\": \"Naciśnij przycisk poniżej aby rozpocząć pobieranie\",\n      \"download_now\": \"Pobierz teraz\",\n      \"download_for\": \"Pobierz dla %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Ustawienia prywatności\",\n      \"header\": \"Ustawienia prywatności\",\n      \"whispers\": { \"label\": \"Szepty\", \"on\": \"Włączone\", \"off\": \"Wyłączone\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Zablokowani użytkownicy\",\n      \"unban\": \"Odblokuj\",\n      \"noBans\": \"Nikt nie został zablokowany\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Opuść aktualny pokój\",\n      \"confirmLeaveRoom\": \"Na pewno chcesz wyjść?\",\n      \"leave\": \"Wyjdź\",\n      \"inviteUsersToRoomBtn\": \"Zaproś użytkowników do pokoju\",\n      \"invite\": \"Zaproś\",\n      \"toggleMuteMicBtn\": \"Przełącz wyciszenie mikrofonu\",\n      \"mute\": \"Wycisz\",\n      \"unmute\": \"Odcisz\",\n      \"makeRoomPublicBtn\": \"Upublicznij pokój!\",\n      \"settings\": \"Ustawienia\",\n      \"speaker\": \"Mówiący\",\n      \"listener\": \"Słuchacz\",\n      \"chat\": \"Czat\",\n      \"toggleDeafMicBtn\": \"Przełącz wyciszenie mikrofonu\",\n      \"deafen\": \"Wycisz dźwięk\",\n      \"undeafen\": \"Odcisz dźwięk\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Twoje urządzenie nie jest obecnie wspierane. Możesz zgłosić to\",\n      \"linkText\": \"na GitHub\",\n      \"addSupport\": \"a ja spróbuję dodać obsługę twojego urządzenia.\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Użytkownicy\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Masz 0 dostępnych znajomych\",\n      \"showMore\": \"Pokaż więcej\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Zaproś!\",\n      \"inviteToRoom\": \"Zaproś do pokoju\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Nie zezwolono na dostęp do mikrofonu (spróbuj zmienić ustawienia twojej przeglądarki i odświeżyć stronę)\",\n      \"dismiss\": \"Odrzuć\",\n      \"tryAgain\": \"Spróbuj ponownie\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Skrót klawiszowy\",\n      \"listening\": \"Słuchanie...\",\n      \"toggleMuteKeybind\": \"Wyciszanie\",\n      \"toggleDeafKeybind\": \"Skrót klawiszowy przełącznika wyciszenia\",\n      \"toggleOverlayKeybind\": \"Nakładka\",\n      \"togglePushToTalkKeybind\": \"Naciśnij i mów\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Brak urządzenia audio\" },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Nadchodzące spotkania w pokojach\",\n      \"exploreMoreRooms\": \"Odkrywaj pokoje\"\n    },\n    \"addToCalendar\": { \"add\": \"Dodaj do kalendarza\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket został zabity przez serwer. Dzieje się tak kiedy otwierasz stronę w nowej karcie.\",\n      \"reconnect\": \"Połącz ponownie\"\n    },\n    \"search\": {\n      \"placeholder\": \"Znajdź pokój, użytkowników lub kategorie\",\n      \"placeholderShort\": \"Szukaj\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Twój profil\",\n      \"language\": \"Język\",\n      \"reportABug\": \"Zgłoś problem\",\n      \"useOldVersion\": \"Użyj starszej wersji\",\n      \"logOut\": {\n        \"button\": \"Wyloguj się\",\n        \"modalSubtitle\": \"Na pewno chcesz się wylogować?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debuguj audio\",\n        \"stopDebugger\": \"Wyłącz debugger\"\n      },\n      \"downloadApp\": \"Pobierz aplikację\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"Wypełnij poniższe pola aby utworzyć pokój\",\n        \"public\": \"Publiczne\",\n        \"private\": \"Prywatne\",\n        \"roomName\": \"Nazwa pokoju\",\n        \"roomDescription\": \"Opis pokoju\",\n        \"descriptionError\": \"Maksymalna długość 500\",\n        \"nameError\": \"Musi być pomiędzy 2 a 60 znakami\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Stworzono nowy pokój\",\n        \"roomInviteFrom\": \"Zaproszenie do pokoju od\",\n        \"justStarted\": \"Właśnie rozpoczeli\",\n        \"likeToJoin\": \", czy chciałbyś dołączyć?\",\n        \"inviteReceived\": \"zostałeś zaproszony do\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Nazwa użytkownika jest już zajęta\",\n        \"avatarUrlError\": \"Nieprawidłowy obraz\",\n        \"avatarUrlLabel\": \"URL do awatara Github/Twitter/Discord\",\n        \"displayNameError\": \"Długość od 2 do 50 znaków\",\n        \"displayNameLabel\": \"Nazwa wyświetlana\",\n        \"usernameError\": \"Długość od 4 do 15 znaków oraz jedynie alphanumeryczna oraz podkreślnik\",\n        \"usernameLabel\": \"Nazwa użytkownika\",\n        \"bioError\": \"Maksymalna długość to 160 znaków\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"URL baneru na Twitterze\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Czy jesteś pewny, że chcesz zablokować użytkownikowi możliwość dołączania do każdego stworzonego przez Ciebie pokoju?\",\n        \"blockUser\": \"Zablokuj użytkownika\",\n        \"makeMod\": \"Dodaj moderatora\",\n        \"unmod\": \"Usuń moderatora\",\n        \"addAsSpeaker\": \"Dodaj jako mówiący\",\n        \"moveToListener\": \"Zmień na słuchacza\",\n        \"unBanFromChat\": \"Odblokuj z czatu\",\n        \"banFromChat\": \"Zablokuj z czatu\",\n        \"banFromRoom\": \"Zablokuj z pokoju\",\n        \"goBackToListener\": \"Powróć do słuchacza\",\n        \"deleteMessage\": \"Usuń tą wiadomość\",\n        \"makeRoomCreator\": \"Awansuj na administratora pokoju\",\n        \"banIPFromRoom\": \"Zablokuj IP z pokoju\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"wymaga pozwolenia na mówienie\",\n        \"makePublic\": \"upublicznij pokój\",\n        \"makePrivate\": \"uczyń pokój prywatnym\",\n        \"renamePublic\": \"Ustaw publiczną nazwę pokoju\",\n        \"renamePrivate\": \"Ustaw prywatną nazwę pokoju\",\n        \"chatDisabled\": \"wyłącz czat\",\n        \"chatCooldown\": \"Odnowienie pisania na czacie (milisekundy)\",\n        \"chat\": {\n          \"label\": \"Czat\",\n          \"enabled\": \"Włączony\",\n          \"disabled\": \"Wyłączony\",\n          \"followerOnly\": \"Tylko dla obserwujących\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Wiadomości\",\n      \"showMore\": \"Pokaż więcej\",\n      \"noMessages\": \"Brak nowych wiadomości\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Twoje pokoje\" },\n    \"scheduledRooms\": {\n      \"title\": \"Zaplanowane pokoje\",\n      \"noneFound\": \"nie znaleziono\",\n      \"allRooms\": \"wszystkie zaplanowane pokoje\",\n      \"myRooms\": \"moje zaplanowane pokoje\",\n      \"scheduleRoomHeader\": \"Zaplanuj pokój\",\n      \"startRoom\": \"Stwórz pokój\",\n      \"tommorow\": \"JUTRO\",\n      \"today\": \"DZISIAJ\",\n      \"modal\": {\n        \"needsFuture\": \"musi być w przyszłości\",\n        \"roomName\": \"Nazwa pokoju\",\n        \"roomDescription\": \"Opis\",\n        \"minLength\": \"Minimalna długość 2\"\n      },\n      \"deleteModal\": {\n        \"areYouSure\": \"Na pewno chcesz usunąć ten zaplanowany pokój??\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Czat\",\n      \"emotesSoon\": \"[emotki wkrótce]\",\n      \"bannedAlert\": \"Zostałeś zablokowany na tym czacie\",\n      \"waitAlert\": \"Musisz poczekać chwilę żeby wysłać kolejną wiadomość\",\n      \"search\": \"Szukaj\",\n      \"searchResults\": \"Wyniki wyszukiwania\",\n      \"recent\": \"Często używany\",\n      \"sendMessage\": \"Wyślij wiadomość\",\n      \"whisper\": \"Szept\",\n      \"welcomeMessage\": \"Witamy na czacie!\",\n      \"roomDescription\": \"Opis pokoju\",\n      \"disabled\": \"czat pokoju został wyłączony\",\n      \"messageDeletion\": {\n        \"message\": \"wiadomość\",\n        \"retracted\": \"wycofana\",\n        \"deleted\": \"usunięta\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Tankowanie rakiety\",\n      \"takingOff\": \"Startowanie\",\n      \"inSpace\": \"W kosmosie\",\n      \"approachingMoon\": \"Zbliża się do Księżyca\",\n      \"lunarDoge\": \"Księżycowy Doge\",\n      \"approachingSun\": \"Zbliża się do słońca\",\n      \"solarDoge\": \"Słoneczny Doge\",\n      \"approachingGalaxy\": \"Zbliża się do galaktyki\",\n      \"galacticDoge\": \"Galaktyczny Doge\",\n      \"spottedLife\": \"Znaleziono planetę z życiem\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/pt-BR/translation.json",
    "content": "{\n  \"_TODO\": \"PRECISA TRADUZIR\",\n  \"common\": {\n    \"loadMore\": \"carregar mais\",\n    \"loading\": \"carregando...\",\n    \"noUsersFound\": \"nenhum usuário encontrado\",\n    \"ok\": \"ok\",\n    \"yes\": \"sim\",\n    \"no\": \"não\",\n    \"cancel\": \"cancelar\",\n    \"save\": \"salvar\",\n    \"edit\": \"editar\",\n    \"delete\": \"deletar\",\n    \"joinRoom\": \"entrar na sala\",\n    \"copyLink\": \"copiar link\",\n    \"copied\": \"copiado\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Esteja ciente que executar o DogeHouse sem as permissões corretas pode causar erros indesejados\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Cabeçalho da interface principal para traduções\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Silenciado | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Conexão tomada\"\n  },\n  \"footer\": {\n    \"link_1\": \"História da Origem\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Reportar um Erro\"\n  },\n  \"pages\": {\n    \"_comment\": \"Interface respectiva da página para tradução\",\n    \"admin\": {\n      \"ban\": \"expulsar\",\n      \"userStaffandContrib\": \"Equipe do usuário & Contribuições\",\n      \"staff\": \"Equipe: \",\n      \"contributions\": \"Contribuições\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"Equipe do usuário\",\n      \"usrContributions\": \"Contribuições do usuário\",\n      \"reason\": \"razão\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista de usuários que você segue que não estão em uma sala privada.\",\n      \"currentRoom\": \"atualmente em:\",\n      \"startPrivateRoom\": \"iniciar uma sala privada com ele\",\n      \"title\": \"Pessoas\"\n    },\n    \"followList\": {\n      \"followHim\": \"seguir\",\n      \"followingHim\": \"seguindo\",\n      \"title\": \"Pessoas\",\n      \"followingNone\": \"Não seguindo ninguém\",\n      \"noFollowers\": \"Sem seguidores\"\n    },\n    \"home\": {\n      \"createRoom\": \"Criar Sala\",\n      \"refresh\": \"Atualizar\",\n      \"editRoom\": \"Editar sala\",\n      \"desktopAlert\": \"Baixe o app desktop DogeHouse hoje mesmo!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"acabou a sala, volte\",\n      \"shareRoomLink\": \"compartilhar link para sala\",\n      \"inviteFollowers\": \"Você pode convidar os seguidores que estão disponíveis:\",\n      \"whenFollowersOnline\": \"Quando seus seguidores estiverem disponíveis eles aparecerão aqui.\"\n    },\n    \"login\": {\n      \"headerText\": \"Levando as conversas de áudio para a lua 🚀\",\n      \"featureText_1\": \"Tema Escuro\",\n      \"featureText_2\": \"Registros abertos\",\n      \"featureText_3\": \"Suporte entre plataformas\",\n      \"featureText_4\": \"Código Aberto\",\n      \"featureText_5\": \"Conversa de Texto\",\n      \"featureText_6\": \"Distribuído por Doge\",\n      \"loginGithub\": \"entrar com GitHub\",\n      \"loginTwitter\": \"entrar com Twitter\",\n      \"createTestUser\": \"criar usuário teste\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"sair\",\n      \"probablyLoading\": \"possívelmente carregando...\",\n      \"voiceSettings\": \"ir para configurações de voz\",\n      \"soundSettings\": \"ir para configurações de áudio\",\n      \"deleteAccount\": \"excluir conta\",\n      \"overlaySettings\": \"vá para as configurações de sobreposição\",\n      \"couldNotFindUser\": \"Desculpa, não foi possível encontrar esse usuário\",\n      \"privacySettings\": \"Configurações de Privacidades\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ops! Essa página se perdeu na conversa.\",\n      \"goHomeMessage\": \"Não esquenta. Você consegue\",\n      \"goHomeLinkText\": \"vá para o início\"\n    },\n    \"room\": {\n      \"speakers\": \"Apresentadores\",\n      \"requestingToSpeak\": \"Solicitando para falar\",\n      \"listeners\": \"Ouvintes\",\n      \"allowAll\": \"Permitir todos\",\n      \"allowAllConfirm\": \"Você tem certeza? Isso permitirá que todos os {{count}} usuários solicitantes falem\"\n    },\n    \"searchUser\": {\n      \"search\": \"buscar...\"\n    },\n    \"soundEffectSettings\": {\n      \"header\": \"Sons\",\n      \"title\": \"Configurações de áudio\",\n      \"playSound\": \"Tocar som\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"editar perfil\",\n      \"followsYou\": \"segue você\",\n      \"followers\": \"seguidores\",\n      \"following\": \"seguindo\",\n      \"followHim\": \"seguir\",\n      \"unfollow\": \"deixar de seguir\",\n      \"followingHim\": \"seguindo\",\n      \"copyProfileUrl\": \"copiar url do perfil\",\n      \"urlCopied\": \"URL copiado para a área de transferência\",\n      \"about\": \"Sobre\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Sobre\",\n        \"rooms\": \"Salas\",\n        \"scheduled\": \"Agendado\",\n        \"recorded\": \"Gravado\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Bloquear\",\n      \"unblock\": \"Desbloquear\",\n      \"sendDM\": \"Enviar DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Configurações do microfone\",\n      \"mic\": \"microfone:\",\n      \"permissionError\": \"não foram encontrados microfones, você não conectou um ou você não deu permissão para este website.\",\n      \"refresh\": \"recarregar lista de microfones\",\n      \"volume\": \"volume:\",\n      \"title\": \"Configurações de voz\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Título de aplicativo inválido\",\n        \"label\": \"Insira o título do aplicativo\"\n      },\n      \"header\": \"Configurações de sobreposição\"\n    },\n    \"download\": {\n      \"starting\": \"Começando o download...\",\n      \"failed\": \"Não foi possível iniciar o download automático, por favor tente novamente mais tarde\",\n      \"visit_gh\": \"Visite o Github Releases\",\n      \"prompt\": \"Clique no botão abaixo para começar a baixar\",\n      \"download_now\": \"Baixe agora\",\n      \"download_for\": \"Baixe para %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Configurações de Privacidade\",\n      \"header\": \"Configurações de Privacidade\",\n      \"whispers\": {\n        \"label\": \"Sussurros\",\n        \"on\": \"On\",\n        \"off\": \"Off\"\n      }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Interface de componente para tradução\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Usuários expulsos\",\n      \"unban\": \"desbanir\",\n      \"noBans\": \"ninguém foi expulso ainda\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Deixar a sala atual\",\n      \"confirmLeaveRoom\": \"Você quer mesmo sair?\",\n      \"leave\": \"Sair\",\n      \"inviteUsersToRoomBtn\": \"Convide usuários para a sala\",\n      \"invite\": \"Convidar\",\n      \"toggleMuteMicBtn\": \"Ativar o mudo do microfone\",\n      \"mute\": \"Mutar\",\n      \"unmute\": \"Desmutar\",\n      \"makeRoomPublicBtn\": \"Deixe a sala pública!\",\n      \"settings\": \"Configurações\",\n      \"speaker\": \"Apresentador\",\n      \"listener\": \"Ouvintes\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Alternar modo Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permissão negada ao tentar acessar o seu microfone (talvez você tenha que ir nas configurações de seu navegador e recarregar a página)\",\n      \"dismiss\": \"fechar\",\n      \"tryAgain\": \"tente novamente\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"público\",\n        \"private\": \"privado\",\n        \"roomName\": \"nome da sala\",\n        \"roomDescription\": \"descrição da sala\",\n        \"descriptionError\": \"comprimento máximo 500\",\n        \"nameError\": \"deve ter entre 2 a 60 caracteres\",\n        \"subtitle\": \"Preencha os campos para iniciar uma nova sala\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nova sala criada\",\n        \"roomInviteFrom\": \"Convite para sala de\",\n        \"justStarted\": \"Acabaram de começar\",\n        \"likeToJoin\": \", você quer entrar?\",\n        \"inviteReceived\": \"você foi convidado para\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"nome de usuário já existente\",\n        \"avatarUrlError\": \"Imagem inválida\",\n        \"avatarUrlLabel\": \"url da foto do Github/Twitter/Discord\",\n        \"displayNameError\": \"de 2 até 50 caracteres\",\n        \"displayNameLabel\": \"Nome de exibição\",\n        \"usernameError\": \"de 4 até 15 caracteres alfanuméricos/sublinhados\",\n        \"usernameLabel\": \"Usuário\",\n        \"bioError\": \"máximo de até 160 caractéres\",\n        \"bioLabel\": \"Biografia\",\n        \"bannerUrlLabel\": \"url do banner do Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Você tem certeza que quer bloquear esse usuário de entrar em qualquer sala que você criar?\",\n        \"blockUser\": \"bloquear usuário\",\n        \"makeMod\": \"promover para moderador\",\n        \"unmod\": \"remover moderador\",\n        \"addAsSpeaker\": \"adicionar como apresentador\",\n        \"moveToListener\": \"mover para ouvinte\",\n        \"banFromChat\": \"expulsar do chat\",\n        \"banFromRoom\": \"expulsar da sala\",\n        \"goBackToListener\": \"voltar para ouvinte\",\n        \"deleteMessage\": \"excluir essa mensagem\",\n        \"makeRoomCreator\": \"criar sala de administração\",\n        \"unBanFromChat\": \"Desbanir do chat\",\n        \"banIPFromRoom\": \"Banir IP da Sala\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"precisar de permissão para falar\",\n        \"makePublic\": \"fazer a sala pública\",\n        \"makePrivate\": \"fazer a sala privada\",\n        \"renamePublic\": \"Definir nome da sala pública\",\n        \"renamePrivate\": \"Definir nome da sala privada\",\n        \"chatDisabled\": \"Chat desabilitado\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Ativado\",\n          \"disabled\": \"Desativado\",\n          \"followerOnly\": \"Somente seguidores\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"não tem áudio por algum motivo\"\n    },\n    \"addToCalendar\": {\n      \"add\": \"Adicionar ao Calendário\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"definir atalho no teclado\",\n      \"listening\": \"ouvindo\",\n      \"toggleMuteKeybind\": \"ativar/desativar atalho para mutar\",\n      \"togglePushToTalkKeybind\": \"ativar/desativar pressione para falar\",\n      \"toggleOverlayKeybind\": \"alternar o atalho de teclado de sobreposição\",\n      \"toggleDeafKeybind\": \"Ative atalho no teclado de deafen\"\n    },\n    \"wsKilled\": {\n      \"description\": \"O Websocket foi finalizado pelo servidor. Isso geralmente acontece quando você abre o site em outra guia.\",\n      \"reconnect\": \"reconectar\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Seu dispositivo não é compatível no momento. Você pode criar uma\",\n      \"linkText\": \"issue no GitHub\",\n      \"addSupport\": \"e vou tentar adicionar suporte para o seu dispositivo.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"convidado\",\n      \"inviteToRoom\": \"convidar para sala\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Pessoas\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Você tem 0 amigos online agora\",\n      \"showMore\": \"Mostrar mais\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Próximas salas\",\n      \"exploreMoreRooms\": \"Explore mais salas\"\n    },\n    \"search\": {\n      \"placeholder\": \"Procure por salas, usuários ou categorias\",\n      \"placeholderShort\": \"Procurar\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Perfil\",\n      \"language\": \"Idioma\",\n      \"reportABug\": \"Reportar um bug\",\n      \"useOldVersion\": \"Usar uma versão antiga\",\n      \"logOut\": {\n        \"button\": \"Sair\",\n        \"modalSubtitle\": \"Você tem certeza que deseja sair?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debugar áudio\",\n        \"stopDebugger\": \"Parar debug\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"Equipe do DogeHouse\",\n      \"dhContributor\": \"Contribuidores do DogeHouse\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Mensagem\",\n      \"showMore\": \"Mostrar mais\",\n      \"noMessages\": \"Não existem novas mensagens\"\n    }\n  },\n  \"modules\": {\n    \"scheduledRooms\": {\n      \"title\": \"Salas agendadas\",\n      \"noneFound\": \"nenhuma encontrada\",\n      \"allRooms\": \"todas as salas agendadas\",\n      \"myRooms\": \"minhas salas agendadas\",\n      \"scheduleRoomHeader\": \"Agendar Sala\",\n      \"startRoom\": \"iniciar sala\",\n      \"modal\": {\n        \"needsFuture\": \"precisa estar no futuro\",\n        \"roomName\": \"nome da sala\",\n        \"minLength\": \"tamanho mínimo: 2\",\n        \"roomDescription\": \"descrição da sala\"\n      },\n      \"tommorow\": \"AMANHÃ\",\n      \"today\": \"HOJE\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Você tem certeza que deseja deletar essa sala agendada?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes em breve]\",\n      \"bannedAlert\": \"Você foi banido do chat\",\n      \"waitAlert\": \"Você tem que esperar um segundo antes de enviar outra mensagem\",\n      \"search\": \"Procurar\",\n      \"searchResults\": \"Procurar Resultados\",\n      \"recent\": \"Usado frequentemente\",\n      \"sendMessage\": \"Envie uma mensagem\",\n      \"whisper\": \"Sussurrar\",\n      \"welcomeMessage\": \"Bem vindo ao chat!\",\n      \"roomDescription\": \"descrição da sala\",\n      \"disabled\": \"O chat da sala foi desabilitado\",\n      \"messageDeletion\": {\n        \"message\": \"mensagem\",\n        \"retracted\": \"retraído\",\n        \"deleted\": \"deletado\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Abastecendo foguete\",\n      \"takingOff\": \"Decolando\",\n      \"inSpace\": \"No espaço\",\n      \"approachingMoon\": \"Chegando na Lua\",\n      \"lunarDoge\": \"Doge lunar\",\n      \"approachingSun\": \"Chegando no Sol\",\n      \"solarDoge\": \"Doge solar\",\n      \"approachingGalaxy\": \"Chegando na galáxia\",\n      \"galacticDoge\": \"Doge galático\",\n      \"spottedLife\": \"Encontrado planeta com vida\"\n    },\n    \"feed\": {\n      \"yourFeed\": \"Seu Feed\"\n    }\n  }\n}"
  },
  {
    "path": "kibbeh/public/locales/pt-PT/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Carregar mais\",\n    \"loading\": \"A carregar...\",\n    \"noUsersFound\": \"Não foram encontrados utilizadores\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Sim\",\n    \"no\": \"Não\",\n    \"cancel\": \"Cancelar\",\n    \"save\": \"Guardar\",\n    \"edit\": \"Editar\",\n    \"delete\": \"Apagar\",\n    \"joinRoom\": \"entrar na sala\",\n    \"copyLink\": \"copiar link\",\n    \"copied\": \"copiado\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Por favor, nota que correr o DogeHouse sem permissões de acesso pode causar erros indesejados\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Silenciado | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"História\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Reportar um Erro\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Banir\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista de utilizadores que segues e que não se encontram numa sala privada.\",\n      \"currentRoom\": \"Atualmente em:\",\n      \"startPrivateRoom\": \"Iniciar uma sala privada com eles\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"seguir\",\n      \"followingHim\": \"a seguir\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Criar sala\",\n      \"refresh\": \"Atualizar\",\n      \"editRoom\": \"Editar sala\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"A sala não existe, voltar atrás\",\n      \"shareRoomLink\": \"Partilhar link da sala\",\n      \"inviteFollowers\": \"Podes convidar os teus seguidores que se encontram online:\",\n      \"whenFollowersOnline\": \"Os teus seguidores que se encontram online irão aparecer aqui.\"\n    },\n    \"login\": {\n      \"headerText\": \"A levar conversas de voz até à lua 🚀\",\n      \"featureText_1\": \"Tema escuro\",\n      \"featureText_2\": \"Registo sem restrições\",\n      \"featureText_3\": \"Suporte multiplataforma\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Mensagens de texto\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Entrar com GitHub\",\n      \"loginTwitter\": \"Entrar com Twitter\",\n      \"createTestUser\": \"Criar utilizador de teste\",\n      \"loginDiscord\": \"Entrar com Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Sair\",\n      \"probablyLoading\": \"Provavlemente a carregar...\",\n      \"voiceSettings\": \"Definições de voz\",\n      \"soundSettings\": \"Definições de som\",\n      \"deleteAccount\": \"Apagar conta\",\n      \"overlaySettings\": \"Ir para definições de Overlay\",\n      \"couldNotFindUser\": \"Desculpa, não conseguimos encontrar esse utilizador\",\n      \"privacySettings\": \"Configurações de Privacidade\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Esta página perdeu-se na conversa.\",\n      \"goHomeMessage\": \"Não te preocupes, podes\",\n      \"goHomeLinkText\": \"Voltar ao início\"\n    },\n    \"room\": {\n      \"speakers\": \"Oradores\",\n      \"requestingToSpeak\": \"A pedir para falar\",\n      \"listeners\": \"Ouvintes\",\n      \"allowAll\": \"Permitir todos\",\n      \"allowAllConfirm\": \"De certeza? Isto permitirá todos os {{count}} utilizadores a pedir, falar\"\n    },\n    \"searchUser\": { \"search\": \"Procurar...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sons\",\n      \"title\": \"Configuração de Som\",\n      \"playSound\": \"Tocar Som\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Editar perfil\",\n      \"followsYou\": \"Seguem-te\",\n      \"followers\": \"Seguidores\",\n      \"following\": \"A seguir\",\n      \"followHim\": \"Seguir\",\n      \"followingHim\": \"Seguindo\",\n      \"copyProfileUrl\": \"copiar URL do perfil\",\n      \"urlCopied\": \"URL copiado\",\n      \"unfollow\": \"Deixar de Seguir\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Programado\",\n        \"recorded\": \"Gravado\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Bloquear\",\n      \"unblock\": \"Desbloquear\",\n      \"sendDM\": \"Mensagem\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Definições de voz\",\n      \"mic\": \"Microfone:\",\n      \"permissionError\": \"Não foram encontrados microfones; ou não foram dadas permissões, ou não estão corretamente ligados.\",\n      \"refresh\": \"Atualizar lista de microfones\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Configurações de Voz\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Título de aplicação inválido\",\n        \"label\": \"Introduza o título da aplicação\"\n      },\n      \"header\": \"Definições de Overlay\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Configurações de Privacidade\",\n      \"header\": \"Configurações de Privacidade\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Utilizadores banidos\",\n      \"unban\": \"Remover ban\",\n      \"noBans\": \"Sem utilizadores banidos\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Sair da sala atual\",\n      \"confirmLeaveRoom\": \"Pretendes sair da sala?\",\n      \"leave\": \"Sair\",\n      \"inviteUsersToRoomBtn\": \"Convidar utilizadores para a sala\",\n      \"invite\": \"Convidar\",\n      \"toggleMuteMicBtn\": \"Silenciar microfone\",\n      \"mute\": \"Silenciar\",\n      \"unmute\": \"Ativar\",\n      \"makeRoomPublicBtn\": \"Tornar a sala pública!\",\n      \"settings\": \"Definições\",\n      \"speaker\": \"Orador\",\n      \"listener\": \"Ouvinte\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Desativar áudio\",\n      \"undeafen\": \"Reativar áudio\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Sem permissão para aceder ao microfone (poderás ter que de ir às configurações do teu navegador e atualizar a página)\",\n      \"dismiss\": \"Ignorar\",\n      \"tryAgain\": \"Tentar novamente\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Público\",\n        \"private\": \"Privado\",\n        \"roomName\": \"nome da sala\",\n        \"roomDescription\": \"descrição da sala\",\n        \"descriptionError\": \"máximo 500 caracteres\",\n        \"nameError\": \"deverá ter entre 2 e 60 caracteres\",\n        \"subtitle\": \"Preenche os campos seguintes para iniciar uma nova sala\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nova sala criada\",\n        \"roomInviteFrom\": \"Convite para a sala de\",\n        \"justStarted\": \"Foi iniciado agora\",\n        \"likeToJoin\": \", gostarias de te juntar?\",\n        \"inviteReceived\": \"Foste convidado para\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"O utilizador já existe\",\n        \"avatarUrlError\": \"Imagem inválida\",\n        \"avatarUrlLabel\": \"URL da foto de perfil (Github/Twitter/Discord)\",\n        \"displayNameError\": \"Deverá ter entre 2 e 50 caracteres\",\n        \"displayNameLabel\": \"Nome de apresentação\",\n        \"usernameError\": \"Deverá ter entre 4 e 15 caracteres alfanuméricos e com letra minúscula\",\n        \"usernameLabel\": \"Nome de utilizador\",\n        \"bioError\": \"Tamanho máximo de 160 caracteres\",\n        \"bioLabel\": \"Biografia\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Pretendes bloquear este utlizador de se juntar às salas que criares?\",\n        \"blockUser\": \"Bloquear utilizador\",\n        \"makeMod\": \"Tornar moderador\",\n        \"unmod\": \"Remover de moderador\",\n        \"addAsSpeaker\": \"Adicionar como orador\",\n        \"moveToListener\": \"Tornar ouvinte\",\n        \"banFromChat\": \"Banir do chat\",\n        \"banFromRoom\": \"Banir da sala\",\n        \"goBackToListener\": \"voltar a ouvinte\",\n        \"deleteMessage\": \"Apagar mensagem\",\n        \"makeRoomCreator\": \"Tornar administrador da sala\",\n        \"unBanFromChat\": \"Desbanir do Chat\",\n        \"banIPFromRoom\": \"Banir IP da Sala\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Pedir permissão para falar\",\n        \"makePublic\": \"Tornar a sala pública\",\n        \"makePrivate\": \"Tornar a sala privada\",\n        \"renamePublic\": \"Definir o nome da sala pública\",\n        \"renamePrivate\": \"Definir o nome da sala privada\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Sem consumidor de áudio devido a um erro desconhecido\"\n    },\n    \"addToCalendar\": { \"add\": \"Adicionar ao calendário\" },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"definir atalho\",\n      \"listening\": \"a ouvir\",\n      \"toggleMuteKeybind\": \"teclas de atalho para silenciar\",\n      \"togglePushToTalkKeybind\": \"teclas de atalho para ativar o premir-para-falar\",\n      \"toggleOverlayKeybind\": \"teclas de atalho para mostrar/esconder o Overlay\",\n      \"toggleDeafKeybind\": \"teclas de atalho para desativar o áudio\"\n    },\n    \"wsKilled\": {\n      \"description\": \"O WebSocket foi quebrado pelo servidor. Isto geralmente acontece quando abres o site noutro separador.\",\n      \"reconnect\": \"restablecer a ligação\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Neste momento o teu dispositivo não é suportado. Podes criar um\",\n      \"linkText\": \"issue no GitHub\",\n      \"addSupport\": \"e tentarei adicionar suporte para o mesmo.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"convidado\",\n      \"inviteToRoom\": \"convidar para a sala\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Pessoas\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Neste momento tens 0 amigos online\",\n      \"showMore\": \"Mostrar mais\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Próximas salas\",\n      \"exploreMoreRooms\": \"Explorar mais salas\"\n    },\n    \"search\": {\n      \"placeholder\": \"Pesquisar salas, utilizadores ou categorias\",\n      \"placeholderShort\": \"Pesquisa\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Perfil\",\n      \"language\": \"Linguagem\",\n      \"reportABug\": \"Reportar um erro\",\n      \"useOldVersion\": \"Usar versão antiga\",\n      \"logOut\": {\n        \"button\": \"Sair\",\n        \"modalSubtitle\": \"De certeza que pretendes sair?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Mensagens\",\n      \"showMore\": \"Mostre Mais\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"scheduledRooms\": {\n      \"title\": \"Salas agendadas\",\n      \"noneFound\": \"nenhuma encontrada\",\n      \"allRooms\": \"todas as salas agendadas\",\n      \"myRooms\": \"salas agendadas por mim\",\n      \"scheduleRoomHeader\": \"Agendar Sala\",\n      \"startRoom\": \"iniciar sala\",\n      \"modal\": {\n        \"needsFuture\": \"a data tem de ser posterior\",\n        \"roomName\": \"nome da sala\",\n        \"minLength\": \"mínimo de 2 caracteres\",\n        \"roomDescription\": \"descrição da sala\"\n      },\n      \"tommorow\": \"AMANHÃ\",\n      \"today\": \"HOJE\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emoticons brevemente]\",\n      \"bannedAlert\": \"Foste banido do chat\",\n      \"waitAlert\": \"Tens de esperar 1 segundo para escrever uma nova mensagem\",\n      \"search\": \"Pesquisar\",\n      \"searchResults\": \"Resultados da pesquisa\",\n      \"recent\": \"Usados Frequentemente\",\n      \"sendMessage\": \"Enviar mensagem\",\n      \"whisper\": \"Sussurro\",\n      \"welcomeMessage\": \"Bem-vindo ao chat!\",\n      \"roomDescription\": \"descrição da sala\",\n      \"disabled\": \"chat foi desativado\",\n      \"messageDeletion\": {\n        \"message\": \"mensagem\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"A abastecer o foguetão\",\n      \"takingOff\": \"A descolar\",\n      \"inSpace\": \"No espaço\",\n      \"approachingMoon\": \"A aproximar-se da lua\",\n      \"lunarDoge\": \"Doge lunar\",\n      \"approachingSun\": \"A aproximar-se do sol\",\n      \"solarDoge\": \"Doge solar\",\n      \"approachingGalaxy\": \"A aproximar-se da galáxia\",\n      \"galacticDoge\": \"Doge galático\",\n      \"spottedLife\": \"Encontrado planeta com vida\"\n    },\n    \"feed\": { \"yourFeed\": \"O teu Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ro/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Încarcă mai multe\",\n    \"loading\": \"Se încarcă...\",\n    \"noUsersFound\": \"Nu s-au găsit utilizatori\",\n    \"ok\": \"ok\",\n    \"yes\": \"da\",\n    \"no\": \"nu\",\n    \"cancel\": \"anulează\",\n    \"save\": \"salvează\",\n    \"edit\": \"editează\",\n    \"delete\": \"șterge\",\n    \"joinRoom\": \"intră în cameră\",\n    \"copyLink\": \"copiază link-ul\",\n    \"copied\": \"copiat\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Vă rugăm să rețineți că rularea DogeHouse fără permisiuni de accesibilitate poate cauza erori nedorite\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Microfon dezactivat | DogeHouse\",\n    \"deafenedTitle\": \"Sunet dezactivat | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Conexiune Luată\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Poveste de origine\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Raportează o eroare\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"blochează\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista utilizatorilor care nu se află într-o cameră privată și pe care o urmăriți.\",\n      \"currentRoom\": \"Deocamdată în:\",\n      \"startPrivateRoom\": \"Începe o cameră privată cu ei\",\n      \"title\": \"Persoane\"\n    },\n    \"followList\": {\n      \"followHim\": \"Urmărește\",\n      \"followingHim\": \"Urmărind\",\n      \"title\": \"Persoane\",\n      \"followingNone\": \"Nu urmărești pe nimeni\",\n      \"noFollowers\": \"Nu ai urmăritorii\"\n    },\n    \"home\": {\n      \"createRoom\": \"Creează cameră\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Editeaza cameră\",\n      \"desktopAlert\": \"Descarcă aplicația DogeHouse pentru desktop!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Camera a dispărut, du-te înapoi\",\n      \"shareRoomLink\": \"Distribuie link-ul camerei\",\n      \"inviteFollowers\": \"Poți invita urmăritorii tăi care sunt online:\",\n      \"whenFollowersOnline\": \"Cănd urmăritorii tăi sunt online, o să apară aici.\"\n    },\n    \"login\": {\n      \"headerText\": \"Ducând conversații vocale către lună 🚀\",\n      \"featureText_1\": \"Temă întunecată\",\n      \"featureText_2\": \"Deschide Sign-Upuri\",\n      \"featureText_3\": \"Suport pe mai multe platformeorm\",\n      \"featureText_4\": \"Sursa deschisa\",\n      \"featureText_5\": \"Conversație Text\",\n      \"featureText_6\": \"Alimentat de Doge\",\n      \"loginGithub\": \"Autentificare cu GitHub\",\n      \"loginTwitter\": \"Autentificare cu Twitter\",\n      \"createTestUser\": \"Creare utilizatori de test\",\n      \"loginDiscord\": \"Autentificare cu Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Deconectați-vă\",\n      \"probablyLoading\": \"Probabil se încarcă...\",\n      \"voiceSettings\": \"Mergi la setările de voce\",\n      \"soundSettings\": \"Mergi la setările de sunet\",\n      \"deleteAccount\": \"Şterge cont\",\n      \"overlaySettings\": \"Accesați setările de suprapunere\",\n      \"couldNotFindUser\": \"Scuze, noi nu am putut gasi acel utilizatori\",\n      \"privacySettings\": \"Setări de Confidențialitate\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Această pagină a fost pierdută în conversație.\",\n      \"goHomeMessage\": \"Nici o grijă. Poți să\",\n      \"goHomeLinkText\": \"te duci acasă\"\n    },\n    \"room\": {\n      \"speakers\": \"Vorbitori\",\n      \"requestingToSpeak\": \"Cere să vorbească\",\n      \"listeners\": \"Ascultători\",\n      \"allowAll\": \"Permiteți-le pe toate\",\n      \"allowAllConfirm\": \"Esti sigur? Asta ar lăsa toti {{count}} utilizatori sa vorbeasca\"\n    },\n    \"searchUser\": { \"search\": \"caută...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sunete\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Editează profil\",\n      \"followsYou\": \"Te urmărește\",\n      \"followers\": \"Urmăritori\",\n      \"following\": \"Urmărind\",\n      \"followHim\": \"Urmărește\",\n      \"followingHim\": \"Urmărind\",\n      \"copyProfileUrl\": \"copiați adresa profilului\",\n      \"urlCopied\": \"adresă copiată\",\n      \"unfollow\": \"Oprește urmărirea\",\n      \"about\": \"Despre\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Despre\",\n        \"rooms\": \"Camere\",\n        \"scheduled\": \"Programate\",\n        \"recorded\": \"Înregistrate\",\n        \"clips\": \"Clipuri\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Blochează\",\n      \"unblock\": \"Deblochează\",\n      \"sendDM\": \"Trimite mesaj privat\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Sunete Voce\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"Nu au fost găsite microfoane - ori nu ai unul conectat ori pagina aceasta nu are permisiune.\",\n      \"refresh\": \"Reîmprospătează lista de microfoane\",\n      \"volume\": \"Volum:\",\n      \"title\": \"Setări Voce\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Titlul de aplicație nu este valid\",\n        \"label\": \"Introduceți Titlul Aplicației\"\n      },\n      \"header\": \"Setări de suprapunere\"\n    },\n    \"download\": {\n      \"starting\": \"Începere descărcarea...\",\n      \"failed\": \"Nu s-a putut descărca automat, încercați mai târziu\",\n      \"visit_gh\": \"Vizitați Github\",\n      \"prompt\": \"Apăsați pe butonul de mai jos pentru a începe descărcarea\",\n      \"download_now\": \"Descărcați Acum\",\n      \"download_for\": \"Descărcați pentru %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Setări de Confidențialitate\",\n      \"header\": \"Setări de Confidențialitate\",\n      \"whispers\": {\n        \"label\": \"Mesaje Private\",\n        \"on\": \"Pornite\",\n        \"off\": \"Oprite\"\n      }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Utilizatori Blocaţi\",\n      \"unban\": \"Elimină restricţie\",\n      \"noBans\": \"Nimeni nu a fost blocat deocamdată\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Părăsește camera curentă\",\n      \"confirmLeaveRoom\": \"Ești sigur că vrei să ieși?\",\n      \"leave\": \"Ieși\",\n      \"inviteUsersToRoomBtn\": \"Invită utilizatori în cameră\",\n      \"invite\": \"Invită\",\n      \"toggleMuteMicBtn\": \"Comutați microfonul\",\n      \"mute\": \"Dezactivare sunet\",\n      \"unmute\": \"Activare sunet\",\n      \"makeRoomPublicBtn\": \"Fă camera publică!\",\n      \"settings\": \"Setări\",\n      \"speaker\": \"Difuzor\",\n      \"listener\": \"Ascultător\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Dispozitivul tău momentan nu este suportat. Poți crea un\",\n      \"linkText\": \"tichet pe GitHub\",\n      \"addSupport\": \"și am să încerc să adaug suport pentru dispozitivul tău.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Invitat\",\n      \"inviteToRoom\": \"Invită în cameră\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Permisiunea refuzată pentru încercarea de a vă accesa microfonul (poate fi necesar să accesați setările browserului și să reîncărcați pagina)\",\n      \"dismiss\": \"Renunță\",\n      \"tryAgain\": \"Încearcă din nou\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Setează keybind\",\n      \"listening\": \"Ascultare\",\n      \"toggleMuteKeybind\": \"Keybind comută microfon\",\n      \"togglePushToTalkKeybind\": \"Keybind comută push-to-talk\",\n      \"toggleOverlayKeybind\": \"keybind comută suprapunere\",\n      \"toggleDeafKeybind\": \"Keybind comută sunet\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"niciun consumator audio dintr-un motiv oarecare\"\n    },\n    \"addToCalendar\": { \"add\": \"Adaugă în Calendar\" },\n    \"wsKilled\": {\n      \"description\": \"Conexiunea a fost oprită de server. Asta se întamplă de obicei cănd website-ul este deschis în altă filă.\",\n      \"reconnect\": \"Reconectează-te\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Publică\",\n        \"private\": \"Privată\",\n        \"roomName\": \"Nume Cameră\",\n        \"roomDescription\": \"Descriere cameră\",\n        \"descriptionError\": \"Lungime maximă de 500\",\n        \"nameError\": \"Trebuie să fie între 2 și 60 caractere\",\n        \"subtitle\": \"Completați următoarele informații pentru a creea o cameră nouă\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Cameră Nouă Creată\",\n        \"roomInviteFrom\": \"Invitație Cameră de la\",\n        \"justStarted\": \"Tocmai au început\",\n        \"likeToJoin\": \", Vrei să te alături?\",\n        \"inviteReceived\": \"Ai fost invitat să\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Nume deja folosit\",\n        \"avatarUrlError\": \"Imagine invalidă\",\n        \"avatarUrlLabel\": \"URL avatar Github/Twitter/Discord\",\n        \"displayNameError\": \"Lungime între 2 și 50 caractere\",\n        \"displayNameLabel\": \"Nume de ecran\",\n        \"usernameError\": \"Lungime între 4 și 15 caractere și numai alphanumerice/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"Lungime maximă de 160 de caractere\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ești sigur că dorești să restrictionezi acest utilizator să intre în orice cameră pe care o creezi?\",\n        \"blockUser\": \"Interzice utilizator\",\n        \"makeMod\": \"Fă mod\",\n        \"unmod\": \"Scoate mod\",\n        \"addAsSpeaker\": \"Adaugă ca vorbitor\",\n        \"moveToListener\": \"Mută la ascultători\",\n        \"banFromChat\": \"Interzice din chat\",\n        \"banFromRoom\": \"Interzice din cameră\",\n        \"goBackToListener\": \"Du-te înapoi la ascultător\",\n        \"deleteMessage\": \"Şterge acest mesaj\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Permite în chat\",\n        \"banIPFromRoom\": \"Interzice IP-ul din Cameră\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Necesită permisiunea de a vorbi\",\n        \"makePublic\": \"Fă camera publică\",\n        \"makePrivate\": \"Fă camera privată\",\n        \"renamePublic\": \"Setează numele camerei publice\",\n        \"renamePrivate\": \"Setează numele camerei private\",\n        \"chatDisabled\": \"dezactivează chat\",\n        \"chatCooldown\": \"Întârziere Chat (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Conversație\",\n          \"enabled\": \"Activat\",\n          \"disabled\": \"Dezactivat\",\n          \"followerOnly\": \"Doar urmăritori\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Persoane\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Ai 0 prieteni online momentan\",\n      \"showMore\": \"Afișati mai multe\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Camere viitoare\",\n      \"exploreMoreRooms\": \"Explorează Mai Multe Camere\"\n    },\n    \"search\": {\n      \"placeholder\": \"Caută camere, utilizatori sau categorii\",\n      \"placeholderShort\": \"Caută\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Pprofil\",\n      \"language\": \"Limbă\",\n      \"reportABug\": \"Raportează o Problemă\",\n      \"useOldVersion\": \"Folosește Versiunea Veche\",\n      \"logOut\": {\n        \"button\": \"Deconectare\",\n        \"modalSubtitle\": \"Sunteți sigur(ă) că vreți să vă deconectați?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Testare Audio\",\n        \"stopDebugger\": \"Oprește Testul\"\n      },\n      \"downloadApp\": \"Descarcă Aplicația\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Mesaje\",\n      \"showMore\": \"Afișează mai multe\",\n      \"noMessages\": \"Niciun mesaj nou\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Camere Programate\",\n      \"noneFound\": \"niciunul nu a fost găsit\",\n      \"allRooms\": \"toate camerele programate\",\n      \"myRooms\": \"camerele mele programate\",\n      \"scheduleRoomHeader\": \"Programează Cameră\",\n      \"startRoom\": \"Începe o cameră\",\n      \"modal\": {\n        \"needsFuture\": \"Trebuie să fie în viitor\",\n        \"roomName\": \"Numele camerei\",\n        \"minLength\": \"De minim 2 caractere\",\n        \"roomDescription\": \"Descriere\"\n      },\n      \"tommorow\": \"MÂINE\",\n      \"today\": \"AZI\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Sigur vreți să ștergeți această cameră programată?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Conversație\",\n      \"emotesSoon\": \"[emote-uri în curând]\",\n      \"bannedAlert\": \"Ai fost interzis din conversație\",\n      \"waitAlert\": \"Trebuie să aștepți o secundă până sa mai trimiți un mesaj.\",\n      \"search\": \"Caută\",\n      \"searchResults\": \"Rezultate Căutare\",\n      \"recent\": \"Frecvent Utilizat\",\n      \"sendMessage\": \"Trimite un mesaj\",\n      \"whisper\": \"Șoptește\",\n      \"welcomeMessage\": \"Bine ai venit în conversație!\",\n      \"roomDescription\": \"Descriere cameră\",\n      \"disabled\": \"conversația a fost dezactivată\",\n      \"messageDeletion\": {\n        \"message\": \"mesaj\",\n        \"retracted\": \"retras\",\n        \"deleted\": \"șters\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Alimentâm racheta\",\n      \"takingOff\": \"Decolăm\",\n      \"inSpace\": \"În spațiu\",\n      \"approachingMoon\": \"Ne apropiem de lună\",\n      \"lunarDoge\": \"Doge lunar\",\n      \"approachingSun\": \"Ne apropiem de soare\",\n      \"solarDoge\": \"Doge solar\",\n      \"approachingGalaxy\": \"Ne apropiem de galaxie\",\n      \"galacticDoge\": \"Doge galactic\",\n      \"spottedLife\": \"Planetă cu viață găsită\"\n    },\n    \"feed\": { \"yourFeed\": \"Fluxul tău\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ru/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Загрузить еще\",\n    \"loading\": \"Загрузка...\",\n    \"noUsersFound\": \"Пользователи не найдены\",\n    \"ok\": \"ОК\",\n    \"yes\": \"Да\",\n    \"no\": \"Нет\",\n    \"cancel\": \"Отмена\",\n    \"save\": \"Сохранить\",\n    \"edit\": \"Редактировать\",\n    \"delete\": \"Удалить\",\n    \"joinRoom\": \"Присоединиться к комнате\",\n    \"copyLink\": \"Скопировать ссылку\",\n    \"copied\": \"Скопировано\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Работа в DogeHouse без разрешений доступа может привести к непредвиденным ошибкам\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Приглушено | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Соединение установлено\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"История создания\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Сообщить об ошибке\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"бан\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Список пользователей, которые не в закрытой комнате и на которых вы подписаны.\",\n      \"currentRoom\": \"Текущая комната\",\n      \"startPrivateRoom\": \"Создать приватную комнату\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Подписаться\",\n      \"followingHim\": \"Вы подписаны\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Создать комнату\",\n      \"refresh\": \"Обновить\",\n      \"editRoom\": \"Редактировать комнату\",\n      \"desktopAlert\": \"Скачайте приложение DogeHouse сегодня!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Комната исчезла, вернитесь назад\",\n      \"shareRoomLink\": \"Поделиться ссылкой на комнату\",\n      \"inviteFollowers\": \"Вы можете пригласить ваших подписчиков, которые сейчас онлайн:\",\n      \"whenFollowersOnline\": \"Когда ваши подписчики онлайн, они отображаются здесь.\"\n    },\n    \"login\": {\n      \"headerText\": \"Возводим голосовое общение до луны 🚀\",\n      \"featureText_1\": \"Темная тема\",\n      \"featureText_2\": \"Открытая регистрация\",\n      \"featureText_3\": \"Кроссплатформенность\",\n      \"featureText_4\": \"Открытый исходный код\",\n      \"featureText_5\": \"Текстовый чат\",\n      \"featureText_6\": \"Работает благодаря Doge\",\n      \"loginGithub\": \"Войти через GitHub\",\n      \"loginTwitter\": \"Войти через Twitter\",\n      \"createTestUser\": \"Создать тестового пользователя\",\n      \"loginDiscord\": \"Войти через Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Выйти\",\n      \"probablyLoading\": \"Вероятно загружается...\",\n      \"voiceSettings\": \"Голосовые настройки\",\n      \"soundSettings\": \"Настройки звука\",\n      \"deleteAccount\": \"Удалить аккаунт\",\n      \"overlaySettings\": \"Настройки оверлея\",\n      \"couldNotFindUser\": \"К сожалению, мы не можем найти этого пользователя\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Упс! Эта страница была потеряна.\",\n      \"goHomeMessage\": \"Не переживайте. Вы можете \",\n      \"goHomeLinkText\": \"вернуться домой\"\n    },\n    \"room\": {\n      \"speakers\": \"Ораторы\",\n      \"requestingToSpeak\": \"Желающие говорить\",\n      \"listeners\": \"Слушатели\",\n      \"allowAll\": \"Разрешить всем\",\n      \"allowAllConfirm\": \"Вы уверены? Это позволит всем {{count}} пользователям, запрашивающих разрешение, говорить\"\n    },\n    \"searchUser\": { \"search\": \"Поиск...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Звуки\",\n      \"title\": \"Настройки звука\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"редактировать профиль\",\n      \"followsYou\": \"подписаны на вас\",\n      \"followers\": \"подписчиков\",\n      \"following\": \"подписок\",\n      \"followHim\": \"подписаться\",\n      \"followingHim\": \"Вы подписаны\",\n      \"copyProfileUrl\": \"скопировать URL профиля\",\n      \"urlCopied\": \"URL профиля скопирован\",\n      \"unfollow\": \"Отписаться\",\n      \"about\": \"About\",\n      \"bot\": \"Бот\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Голосовые настройки\",\n      \"mic\": \"микрофон:\",\n      \"permissionError\": \"микрофон не найден, он не подключен или браузеру не дано соответствующее разрешение.\",\n      \"refresh\": \"обновить список микрофонов\",\n      \"volume\": \"громкость:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Неправильное название приложения\",\n        \"label\": \"Введите название приложения\"\n      },\n      \"header\": \"Настройки оверлея\"\n    },\n    \"download\": {\n      \"starting\": \"Начало загрузки...\",\n      \"failed\": \"Не удалось выполнить автоматическую загрузку, повторите попытку позже\",\n      \"visit_gh\": \"Посетите Github Releases\",\n      \"prompt\": \"Нажмите на кнопку ниже, чтобы начать загрузку\",\n      \"download_now\": \"Скачать сейчас\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Заблокированные пользователи\",\n      \"unban\": \"разбанить\",\n      \"noBans\": \"еще никто не был забанен\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Покинуть данную комнату\",\n      \"confirmLeaveRoom\": \"Вы уверены, что хотите выйти?\",\n      \"leave\": \"Выйти\",\n      \"inviteUsersToRoomBtn\": \"Пригласить пользователей в комнату\",\n      \"invite\": \"Пригласить\",\n      \"toggleMuteMicBtn\": \"Переключить состояние микрофона\",\n      \"mute\": \"Выключить звук\",\n      \"unmute\": \"Включить звук\",\n      \"makeRoomPublicBtn\": \"Сделать комнату открытой!\",\n      \"settings\": \"настройки\",\n      \"speaker\": \"оратор\",\n      \"listener\": \"слушатель\",\n      \"chat\": \"Чат\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Заглушить\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Ваше устройство не поддерживается. Вы можете создать\",\n      \"linkText\": \"запрос через GitHub\",\n      \"addSupport\": \"и мы попробуем добавить поддержку для вашего устройства.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"приглашен\",\n      \"inviteToRoom\": \"пригласить в комнату\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Не удается получить доступ к вашему микрофону (возможно, вам следует зайти в настройки браузера или перезагрузить страницу)\",\n      \"dismiss\": \"отклонить\",\n      \"tryAgain\": \"попробовать еще раз\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"назначить клавишу\",\n      \"listening\": \"слушает\",\n      \"toggleMuteKeybind\": \"Активация микрофона при нажатии\",\n      \"togglePushToTalkKeybind\": \"Включить режим рации\",\n      \"toggleOverlayKeybind\": \"переключить привязку клавиши оверлея\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"нет аудиосвязи\" },\n    \"addToCalendar\": { \"add\": \"Добавить в Календарь\" },\n    \"wsKilled\": {\n      \"description\": \"Вебсокет был удален сервером. Обычно это происходит когда вы открываете сайт в другой вкладке.\",\n      \"reconnect\": \"Переподключиться\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"публичная\",\n        \"private\": \"приватная\",\n        \"roomName\": \"название комнаты\",\n        \"roomDescription\": \"описание комнаты\",\n        \"descriptionError\": \"максимальная длина 500\",\n        \"nameError\": \"длина должна быть от 2 до 60 символов\",\n        \"subtitle\": \"Заполните следующие поля чтобы создать новую комнату\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Новая комната создана\",\n        \"roomInviteFrom\": \"Приглашение в комнату от\",\n        \"justStarted\": \"Они только что начали\",\n        \"likeToJoin\": \", хотите присоединиться?\",\n        \"inviteReceived\": \"вы были приглашены в\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"имя пользователя занято\",\n        \"avatarUrlError\": \"Некорректное изображение\",\n        \"avatarUrlLabel\": \"URL аватара из Github/Twitter/Discord\",\n        \"displayNameError\": \"длина от 2 до 50 символов\",\n        \"displayNameLabel\": \"Публичное имя\",\n        \"usernameError\": \"длина от 4 до 15 символов, только буквенно-цифровые символы и нижнее подчеркивание\",\n        \"usernameLabel\": \"Имя пользователя\",\n        \"bioError\": \"максимальная длина 160 символов\",\n        \"bioLabel\": \"Обо мне\",\n        \"bannerUrlLabel\": \"URL баннера Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Вы уверены, что хотите закрыть этому пользователя доступ в любую из ваших комнат?\",\n        \"blockUser\": \"заблокировать пользователя\",\n        \"makeMod\": \"назначить модератором\",\n        \"unmod\": \"забрать модератора\",\n        \"addAsSpeaker\": \"добавить оратора\",\n        \"moveToListener\": \"переместить в слушатели\",\n        \"banFromChat\": \"забанить из этого чата\",\n        \"banFromRoom\": \"забанить из этой комнаты\",\n        \"goBackToListener\": \"стать слушателем\",\n        \"deleteMessage\": \"удалить это сообщение\",\n        \"makeRoomCreator\": \"назначить админом комнаты\",\n        \"unBanFromChat\": \"Разбанить в чате\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"чтобы разговаривать здесь требуется разрешение\",\n        \"makePublic\": \"сделать комнату публичной\",\n        \"makePrivate\": \"сделать комнату приватной\",\n        \"renamePublic\": \"Установить название общей комнаты\",\n        \"renamePrivate\": \"Установить название приватной комнаты\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Люди\",\n      \"online\": \"Онлайн\",\n      \"noOnline\": \"В данный момент 0 ваших друзей онлайн\",\n      \"showMore\": \"Показать больше\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Ожидаемые комнаты\",\n      \"exploreMoreRooms\": \"Найти больше комнат\"\n    },\n    \"search\": {\n      \"placeholder\": \"Поиск комнат, пользователей или категорий\",\n      \"placeholderShort\": \"Поиск\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Профиль\",\n      \"language\": \"Язык\",\n      \"reportABug\": \"Сообщить о проблеме\",\n      \"useOldVersion\": \"Использовать старую версию\",\n      \"logOut\": {\n        \"button\": \"Выход\",\n        \"modalSubtitle\": \"Вы уверены что хотите выйти?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Отладка звука\",\n        \"stopDebugger\": \"Остановить отладчик\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Запланированные комнаты\",\n      \"noneFound\": \"не найдено\",\n      \"allRooms\": \"все запланированные комнаты\",\n      \"myRooms\": \"мои запланированные комнаты\",\n      \"scheduleRoomHeader\": \"Запланированные комнаты\",\n      \"startRoom\": \"создать комнату\",\n      \"modal\": {\n        \"needsFuture\": \"должно появится в будущем\",\n        \"roomName\": \"название комнаты\",\n        \"roomDescription\": \"Описание\",\n        \"minLength\": \"минимальная длина 2\"\n      },\n      \"tommorow\": \"Завтра\",\n      \"today\": \"Сегодня\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Вы действительно хотите удалить эту запланированную комнату?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Чат\",\n      \"emotesSoon\": \"[эмодзи скоро]\",\n      \"bannedAlert\": \"Вы были заблокированы в этом чате\",\n      \"waitAlert\": \"Подождите еще секунду, прежде чем отправить следующее сообщение\",\n      \"search\": \"Поиск\",\n      \"searchResults\": \"Результаты поиска\",\n      \"recent\": \"Часто используемые\",\n      \"sendMessage\": \"Отправить сообщение\",\n      \"whisper\": \"Шепнуть\",\n      \"welcomeMessage\": \"Добро пожаловать в чат!\",\n      \"roomDescription\": \"описание комнаты\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"заправляем ракету\",\n      \"takingOff\": \"старт двигателей\",\n      \"inSpace\": \"В космосе\",\n      \"approachingMoon\": \"приближаемся к луне\",\n      \"lunarDoge\": \"лунный Doge\",\n      \"approachingSun\": \"приближаемся к солнцу\",\n      \"solarDoge\": \"солнечный Doge\",\n      \"approachingGalaxy\": \"приближаемся к галактике\",\n      \"galacticDoge\": \"галактический Doge\",\n      \"spottedLife\": \"замечена планета с жизнью\"\n    },\n    \"feed\": { \"yourFeed\": \"Ваша лента\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sa/translation.json",
    "content": "{\r\n  \"_comment\": \"if you change this file, do: yarn i18\",\r\n  \"common\": {\r\n    \"loadMore\": \"भार मोरे\",\r\n    \"loading\": \"लॉदिङ्ग्...\",\r\n    \"noUsersFound\": \"नो उसेर्स फ़ोउन्द्!\",\r\n    \"ok\": \"ओक\",\r\n    \"yes\": \"येस\",\r\n    \"no\": \"नो\",\r\n    \"cancel\": \"चान्च्ले\",\r\n    \"save\": \"सवे\",\r\n    \"edit\": \"एदित\",\r\n    \"delete\": \"देलेते\",\r\n    \"joinRoom\": \"जोइन् रूम\",\r\n    \"copyLink\": \"चोप्य लिनक्\",\r\n    \"copied\": \"चोप्पिएद्!\",\r\n    \"copy\": \"चोप्य\",\r\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\r\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\r\n    \"requestPermissions\": \"फ्लेअसे नोते रुन्निन्ग डोगेहोउसे विथोउत अच्छेस्सिबिलित्य पेर्मिस्सिओन्स् मय चौसे उन्वन्तेद एर्रोर्स्\",\r\n    \"error\": \"Error\"\r\n  },\r\n  \"header\": {\r\n    \"_comment\": \"Main Header UI Internationalization Strings\",\r\n    \"title\": \"डोगेःओउसे\",\r\n    \"dashboard\": \"डश्बॉर्द्\",\r\n    \"connectionTaken\": \"ओन्नेच्तिओन् तकेन\",\r\n    \"mutedTitle\": \"मुतेद | डोगेःओउसे\",\r\n    \"deafenedTitle\": \"डेअफ़ेनेद् | डोगेःओउसे\"\r\n  },\r\n  \"footer\": {\r\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\r\n    \"link_1\": \"Origin Story\",\r\n    \"link_2\": \"Discord\",\r\n    \"link_3\": \"Report a Bug\"\r\n  },\r\n  \"pages\": {\r\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\r\n    \"botEdit\": {\r\n      \"yourBots\": \"Your Bots\",\r\n      \"bots\": \"Bots\",\r\n      \"title\": \"Bot Information\",\r\n      \"apiKey\": \"ApiKey\",\r\n      \"regenerate\": \"Regenerate\",\r\n      \"reveal\": \"Click to reveal ApiKey\"\r\n    },\r\n    \"admin\": {\r\n      \"ban\": \"Ban\",\r\n      \"userStaffandContrib\": \"User Staff & Contributions\",\r\n      \"staff\": \"Staff: \",\r\n      \"contributions\": \"Contributions\",\r\n      \"username\": \"Username\",\r\n      \"usrStaff\": \"User Staff\",\r\n      \"usrContributions\": \"User Contributions\",\r\n      \"reason\": \"reason\",\r\n      \"usernamePlaceholder\": \"username to perform actions on\"\r\n    },\r\n    \"download\": {\r\n      \"prompt\": \"Download the DogeHouse Desktop app\",\r\n      \"download_for\": \"Download for %platform% (%ext%)\",\r\n      \"failed\": \"Unable to detect platform, please try again later or visit GitHub Releases\",\r\n      \"visit_gh\": \"Visit GitHub Releases\"\r\n    },\r\n    \"followingOnlineList\": {\r\n      \"title\": \"People\",\r\n      \"listHeader\": \"List of users that are not in a private room and you follow.\",\r\n      \"currentRoom\": \"Currently in:\",\r\n      \"startPrivateRoom\": \"Start a private room with them\"\r\n    },\r\n    \"followList\": {\r\n      \"title\": \"People\",\r\n      \"followHim\": \"Follow\",\r\n      \"followingHim\": \"Following\",\r\n      \"followingNone\": \"Not following anyone\",\r\n      \"noFollowers\": \"No followers\"\r\n    },\r\n    \"home\": {\r\n      \"createRoom\": \"New room\",\r\n      \"editRoom\": \"Edit Room\",\r\n      \"refresh\": \"Refresh\",\r\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\r\n    },\r\n    \"inviteList\": {\r\n      \"roomGone\": \"Room gone; go back\",\r\n      \"shareRoomLink\": \"Share Room Link\",\r\n      \"inviteFollowers\": \"You can invite your followers that are online:\",\r\n      \"whenFollowersOnline\": \"When your followers are online, they will show up here.\"\r\n    },\r\n    \"login\": {\r\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\r\n      \"featureText_1\": \"Dark Theme\",\r\n      \"featureText_2\": \"Open Sign-Ups\",\r\n      \"featureText_3\": \"Cross-Platform Support\",\r\n      \"featureText_4\": \"Open Source\",\r\n      \"featureText_5\": \"Text Chat\",\r\n      \"featureText_6\": \"Powered by Doge\",\r\n      \"loginGithub\": \"Login with GitHub\",\r\n      \"loginTwitter\": \"Login with Twitter\",\r\n      \"loginDiscord\": \"Login with Discord\",\r\n      \"createTestUser\": \"Create Test User\"\r\n    },\r\n    \"myProfile\": {\r\n      \"logout\": \"Logout\",\r\n      \"probablyLoading\": \"probably loading...\",\r\n      \"voiceSettings\": \"Voice Settings\",\r\n      \"overlaySettings\": \"Overlay Settings\",\r\n      \"soundSettings\": \"Sound Settings\",\r\n      \"deleteAccount\": \"Delete Account\",\r\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\r\n      \"privacySettings\": \"Privacy Settings\"\r\n    },\r\n    \"notFound\": {\r\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\r\n      \"goHomeMessage\": \"Not to worry. You can\",\r\n      \"goHomeLinkText\": \"go home\"\r\n    },\r\n    \"privacySettings\": {\r\n      \"title\": \"Privacy Settings\",\r\n      \"header\": \"Privacy Settings\",\r\n      \"whispers\": {\r\n        \"label\": \"Whispers\",\r\n        \"on\": \"On\",\r\n        \"off\": \"Off\"\r\n      }\r\n    },\r\n    \"room\": {\r\n      \"speakers\": \"Speakers\",\r\n      \"requestingToSpeak\": \"Requesting to Speak\",\r\n      \"listeners\": \"Listeners\",\r\n      \"allowAll\": \"Allow all\",\r\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\r\n    },\r\n    \"searchUser\": {\r\n      \"search\": \"search...\"\r\n    },\r\n    \"soundEffectSettings\": {\r\n      \"title\": \"Sound Settings\",\r\n      \"header\": \"Sounds\",\r\n      \"playSound\": \"Play Sound\"\r\n    },\r\n    \"viewUser\": {\r\n      \"editProfile\": \"Edit Profile\",\r\n      \"followsYou\": \"Follows you\",\r\n      \"followers\": \"followers\",\r\n      \"following\": \"following\",\r\n      \"followHim\": \"Follow\",\r\n      \"unfollow\": \"Unfollow\",\r\n      \"followingHim\": \"Following\",\r\n      \"block\": \"Block\",\r\n      \"unblock\": \"Unblock\",\r\n      \"sendDM\": \"Send DM\",\r\n      \"copyProfileUrl\": \"Copy Profile URL\",\r\n      \"urlCopied\": \"URL copied to clipboard\",\r\n      \"about\": \"About\",\r\n      \"aboutSuffix\": \"\",\r\n      \"bot\": \"Bot\",\r\n      \"errors\": {\r\n        \"blocked\": \"This user has blocked you.\",\r\n\r\n        \"default\": \"Whoops! We couldn't load this user.\"\r\n      },\r\n      \"profileTabs\": {\r\n        \"about\": \"About\",\r\n        \"rooms\": \"Rooms\",\r\n        \"scheduled\": \"Scheduled\",\r\n        \"recorded\": \"Recorded\",\r\n        \"clips\": \"Clips\",\r\n        \"admin\": \"Admin\"\r\n      }\r\n    },\r\n    \"voiceSettings\": {\r\n      \"title\": \"Voice Settings\",\r\n      \"header\": \"Voice Settings\",\r\n      \"mic\": \"Mic:\",\r\n      \"permissionError\": \"No mics found, you either have none plugged in or haven't given this website permission.\",\r\n      \"refresh\": \"Refresh Mic List\",\r\n      \"volume\": \"Volume:\"\r\n    },\r\n    \"overlaySettings\": {\r\n      \"header\": \"Overlay Settings\",\r\n      \"input\": {\r\n        \"errorMsg\": \"Please enter a valid app title\",\r\n        \"label\": \"App title\"\r\n      }\r\n    }\r\n  },\r\n  \"components\": {\r\n    \"_comment\": \"Component UI Internationalization Strings\",\r\n    \"avatar\": {},\r\n    \"backBar\": {},\r\n    \"blockedFromRoomUsers\": {\r\n      \"header\": \"Banned Users\",\r\n      \"unban\": \"Unban\",\r\n      \"noBans\": \"No one has been banned yet\"\r\n    },\r\n    \"bottomVoiceControl\": {\r\n      \"leaveCurrentRoomBtn\": \"Leave Current Room\",\r\n      \"confirmLeaveRoom\": \"Are you sure you want to leave?\",\r\n      \"leave\": \"Leave\",\r\n      \"inviteUsersToRoomBtn\": \"Invite Users to Room\",\r\n      \"invite\": \"Invite\",\r\n      \"toggleMuteMicBtn\": \"Toggle Mute\",\r\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\r\n      \"mute\": \"Mute\",\r\n      \"unmute\": \"Unmute\",\r\n      \"deafen\": \"Deafen\",\r\n      \"undeafen\": \"Undeafen\",\r\n      \"makeRoomPublicBtn\": \"Make Room Public!\",\r\n      \"settings\": \"Settings\",\r\n      \"speaker\": \"Speaker\",\r\n      \"listener\": \"Listener\",\r\n      \"chat\": \"Chat\"\r\n    },\r\n    \"deviceNotSupported\": {\r\n      \"notSupported\": \"Your device is currently not supported. You can create an\",\r\n      \"linkText\": \"issue on GitHub\",\r\n      \"addSupport\": \"and I will try adding support for your device.\"\r\n    },\r\n    \"followingOnline\": {\r\n      \"people\": \"People\",\r\n      \"online\": \"ONLINE\",\r\n      \"noOnline\": \"You have 0 friends online right now\",\r\n      \"showMore\": \"Show more\"\r\n    },\r\n    \"inviteButton\": {\r\n      \"invited\": \"Invited!\",\r\n      \"inviteToRoom\": \"Invite to room\"\r\n    },\r\n    \"messagesDropdown\": {\r\n      \"title\": \"Messages\",\r\n      \"showMore\": \"Show More\",\r\n      \"noMessages\": \"No new messages\"\r\n    },\r\n    \"micPermissionBanner\": {\r\n      \"permissionDenied\": \"Permission denied trying to access your mic (you may need to go into browser settings and reload the page)\",\r\n      \"dismiss\": \"Dismiss\",\r\n      \"tryAgain\": \"Try Again\"\r\n    },\r\n    \"keyboardShortcuts\": {\r\n      \"setKeybind\": \"Set Keybind\",\r\n      \"listening\": \"Listening...\",\r\n      \"toggleMuteKeybind\": \"Toggle mute keybind\",\r\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\",\r\n      \"toggleOverlayKeybind\": \"Toggle overlay keybind\",\r\n      \"togglePushToTalkKeybind\": \"Toggle push-to-talk keybind\"\r\n    },\r\n    \"userVolumeSlider\": {\r\n      \"noAudioMessage\": \"No audio consumer for some reason\"\r\n    },\r\n    \"upcomingRoomsCard\": {\r\n      \"upcomingRooms\": \"Upcoming rooms\",\r\n      \"exploreMoreRooms\": \"Explore more rooms\"\r\n    },\r\n    \"addToCalendar\": {\r\n      \"add\": \"Add to Calendar\"\r\n    },\r\n    \"wsKilled\": {\r\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\r\n      \"reconnect\": \"Reconnect\"\r\n    },\r\n    \"search\": {\r\n      \"placeholder\": \"Search for rooms, users or categories\",\r\n      \"placeholderShort\": \"Search\"\r\n    },\r\n    \"settingsDropdown\": {\r\n      \"profile\": \"Profile\",\r\n      \"language\": \"Language\",\r\n      \"reportABug\": \"Report A Bug\",\r\n      \"useOldVersion\": \"Use Old Version\",\r\n      \"logOut\": {\r\n        \"button\": \"Log out\",\r\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\r\n      },\r\n      \"debugAudio\": {\r\n        \"debugAudio\": \"Debug Audio\",\r\n        \"stopDebugger\": \"Stop Debugger\"\r\n      },\r\n      \"downloadApp\": \"Download App\",\r\n      \"developer\": \"Developer Settings\"\r\n    },\r\n    \"userBadges\": {\r\n      \"dhStaff\": \"DogeHouse Staff\",\r\n      \"dhContributor\": \"DogeHouse Contributor\"\r\n    },\r\n    \"modals\": {\r\n      \"createBotModal\": {\r\n        \"usernameTaken\": \"Username is taken\",\r\n        \"subtitle\": \"Please fill the details below to create your bot\",\r\n        \"title\": \"Create Bot\"\r\n      },\r\n      \"createRoomModal\": {\r\n        \"subtitle\": \"Fill the following fields to start a new room\",\r\n        \"public\": \"Public\",\r\n        \"private\": \"Private\",\r\n        \"roomName\": \"Room name\",\r\n        \"roomDescription\": \"Room description\",\r\n        \"descriptionError\": \"Max length 500\",\r\n        \"nameError\": \"Must be between 2 to 60 characters long\"\r\n      },\r\n      \"invitedToJoinRoomModal\": {\r\n        \"newRoomCreated\": \"New Room Created\",\r\n        \"roomInviteFrom\": \"Room Invite from\",\r\n        \"justStarted\": \"They just started\",\r\n        \"likeToJoin\": \", would you like to join?\",\r\n        \"inviteReceived\": \"you've been invited to\"\r\n      },\r\n      \"editProfileModal\": {\r\n        \"usernameTaken\": \"Username taken\",\r\n        \"avatarUrlError\": \"Invalid image\",\r\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar URL\",\r\n        \"bannerUrlLabel\": \"Twitter banner URL\",\r\n        \"displayNameError\": \"Length 2 to 50 characters\",\r\n        \"displayNameLabel\": \"Display Name\",\r\n        \"usernameError\": \"Length 4 to 15 characters and only alphanumeric/underscore\",\r\n        \"usernameLabel\": \"Username\",\r\n        \"bioError\": \"Max length of 160 characters\",\r\n        \"bioLabel\": \"Bio\"\r\n      },\r\n      \"profileModal\": {\r\n        \"blockUserConfirm\": \"Are you sure you want to block this user from joining any room you ever create?\",\r\n        \"blockUser\": \"Block user\",\r\n        \"makeMod\": \"Promote to Mod\",\r\n        \"unmod\": \"Demote from Mod\",\r\n        \"addAsSpeaker\": \"Add as Speaker\",\r\n        \"moveToListener\": \"Move to Listener\",\r\n        \"unBanFromChat\": \"Unban from Chat\",\r\n        \"banFromChat\": \"Ban from Chat\",\r\n        \"banFromRoom\": \"Ban from Room\",\r\n        \"banIPFromRoom\": \"Ban IP from Room\",\r\n        \"goBackToListener\": \"Go Back to Listener\",\r\n        \"deleteMessage\": \"Delete this Message\",\r\n        \"makeRoomCreator\": \"Promote to Admin\"\r\n      },\r\n      \"roomSettingsModal\": {\r\n        \"requirePermission\": \"require permission to speak\",\r\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\r\n        \"chat\": {\r\n          \"label\": \"Chat\",\r\n          \"enabled\": \"Enabled\",\r\n          \"disabled\": \"Disabled\",\r\n          \"followerOnly\": \"Follower Only\"\r\n        },\r\n        \"makePublic\": \"make room public\",\r\n        \"makePrivate\": \"make room private\",\r\n        \"renamePublic\": \"Set public room name\",\r\n        \"renamePrivate\": \"Set private room name\"\r\n      }\r\n    }\r\n  },\r\n  \"modules\": {\r\n    \"_comment\": \"Modules UI Internationalization Strings\",\r\n    \"feed\": {\r\n      \"yourFeed\": \"Your feed\"\r\n    },\r\n    \"scheduledRooms\": {\r\n      \"title\": \"Scheduled Rooms\",\r\n      \"noneFound\": \"none found\",\r\n      \"allRooms\": \"all scheduled rooms\",\r\n      \"myRooms\": \"my scheduled rooms\",\r\n      \"scheduleRoomHeader\": \"Schedule Room\",\r\n      \"startRoom\": \"start room\",\r\n      \"tommorow\": \"TOMMOROW\",\r\n      \"today\": \"TODAY\",\r\n      \"modal\": {\r\n        \"needsFuture\": \"needs to be in the future\",\r\n        \"roomName\": \"room name\",\r\n        \"roomDescription\": \"Description\",\r\n        \"minLength\": \"min length 2\"\r\n      },\r\n      \"deleteModal\": {\r\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\r\n      }\r\n    },\r\n    \"roomChat\": {\r\n      \"title\": \"Chat\",\r\n      \"emotesSoon\": \"[emotes soon]\",\r\n      \"bannedAlert\": \"You have been banned from chat\",\r\n      \"waitAlert\": \"You have to wait a second before sending another message\",\r\n      \"search\": \"Search\",\r\n      \"searchResults\": \"Search Results\",\r\n      \"recent\": \"Frequently Used\",\r\n      \"sendMessage\": \"Send a Message\",\r\n      \"whisper\": \"Whisper\",\r\n      \"welcomeMessage\": \"Welcome to chat!\",\r\n      \"roomDescription\": \"Room description\",\r\n      \"disabled\": \"room chat has been disabled\",\r\n      \"messageDeletion\": {\r\n        \"message\": \"message\",\r\n        \"retracted\": \"retracted\",\r\n        \"deleted\": \"deleted\"\r\n      }\r\n    },\r\n    \"roomStatus\": {\r\n      \"fuelingRocket\": \"Fueling rocket\",\r\n      \"takingOff\": \"Taking off\",\r\n      \"inSpace\": \"In space\",\r\n      \"approachingMoon\": \"Approaching moon\",\r\n      \"lunarDoge\": \"Lunar Doge\",\r\n      \"approachingSun\": \"Approaching sun\",\r\n      \"solarDoge\": \"Solar Doge\",\r\n      \"approachingGalaxy\": \"Approaching galaxy\",\r\n      \"galacticDoge\": \"Galactic Doge\",\r\n      \"spottedLife\": \"Planet with life spotted\"\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "kibbeh/public/locales/si/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"තවත් පෙන්නන්න\",\n    \"loading\": \"ලබා ගනිමින් පවතී...\",\n    \"noUsersFound\": \"පරිශීලකයින් හමුවූයේ නැත\",\n    \"ok\": \"හරි\",\n    \"yes\": \"ඔව්\",\n    \"no\": \"නැත\",\n    \"cancel\": \"අවලංගු කරන්න\",\n    \"save\": \"සුරකින්න\",\n    \"edit\": \"සංස්කරණය කරන්න\",\n    \"delete\": \"මකන්න\",\n    \"joinRoom\": \"සමූහයට එක්වන්න\",\n    \"copyLink\": \"සබැඳිය පිටපත් කරන්න\",\n    \"copied\": \"පිටපත් කරගත්තා\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"මූලාරම්භක කතාව\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"ගැටලුවක් පැමිණිලි කරන්න\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ඉවත් කරන්න\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"පෞද්ගලික සමූහයක නොමැති ඔබ හඹායන පරිශීලකයින්\",\n      \"currentRoom\": \"දැනට සිටින්නේ:\",\n      \"startPrivateRoom\": \"ඔවුන් සමග පෞද්ගලික සමූහයක් අරඹන්න\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"හඹා යන්න\",\n      \"followingHim\": \"හඹා යන්නන්\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"සමූහයක් අරඹන්න\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"සමූහය නොමැත,ආපසු යන්න\",\n      \"shareRoomLink\": \"සමූහයේ සබැඳිය බෙදාගන්න\",\n      \"inviteFollowers\": \"ඔබට සක්‍රීයව සිටින ඔබව හඹායන්නන්ට ඇරයුම් කල හැක:\",\n      \"whenFollowersOnline\": \"ඔබව හඹායන්නන් සක්‍රීයව සිටින විට,ඔවුන්ව මෙතන පෙන්වයි.\"\n    },\n    \"login\": {\n      \"headerText\": \"ඔබගේ හඬ සංවාද හඳ දක්වා ගෙනියන්න 🚀\",\n      \"featureText_1\": \"අඳුරු ප්‍රකාරය\",\n      \"featureText_2\": \"විවෘත සම්බන්ධ වීම්\",\n      \"featureText_3\": \"සියලු පද්ධති වලට සහය\",\n      \"featureText_4\": \"විවෘත කේත\",\n      \"featureText_5\": \"පණිවුඩ\",\n      \"featureText_6\": \"බලගැන්වෙන්නේ Doge මගින්\",\n      \"loginGithub\": \" GitHub මගින් සම්බන්ධ වන්න\",\n      \"loginTwitter\": \"Twitter මගින් සම්බන්ධ වන්න\",\n      \"createTestUser\": \"පරිශීලක පරීක්ෂකයෙක් සාදන්න\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"ඉවත් වන්න\",\n      \"probablyLoading\": \"බොහෝවිට ලබා ගනිමින් පවතී...\",\n      \"voiceSettings\": \"හඬ සැකසුම් වලට යන්න\",\n      \"soundSettings\": \"ශබ්ද සැකසුම් වලට යන්න\",\n      \"deleteAccount\": \"ගිණුම ඉවත් කරන්න\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! සංවාදයේදී මෙම පිටුව මඟහැරී ඇත.\",\n      \"goHomeMessage\": \"කරදර වෙන්න දෙයක් නොමැත.ඔබට\",\n      \"goHomeLinkText\": \"මුල් පිටුවට යන්න\"\n    },\n    \"room\": {\n      \"speakers\": \"Speakers\",\n      \"requestingToSpeak\": \"කතා කිරීමට අවසර ලබා ගන්න\",\n      \"listeners\": \"Listeners\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"සොයන්න...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"ශබ්ද\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"profile එක සංස්කරණය කරන්න\",\n      \"followsYou\": \"ඔබව හඹා යයි\",\n      \"followers\": \"ඔබව හඹා යන්නන්\",\n      \"following\": \"ඔබ හඹා යන්නන්\",\n      \"followHim\": \"හඹා යන්න\",\n      \"followingHim\": \"හඹා යන්නන්\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"හඬ සැකසුම්\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"mics එකක් හමුවූයේ නැත.ඔබ එකක් සම්බන්ධ නොමැත හෝ මෙම වෙබ් අඩවියට අවසර දී නොමැත\",\n      \"refresh\": \"mic ලැයිස්තුව refresh කරන්න\",\n      \"volume\": \"පරිමාව:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"අවහිර කල පරිශීලකයින්\",\n      \"unban\": \"අවහිර නොකරන්න\",\n      \"noBans\": \"දැනට කිසිවෙක් අවහිර කර නොමැත\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"දැනට සිටින සමූහයෙන් ඉවත් වන්න\",\n      \"confirmLeaveRoom\": \"ඔබට ඉවත් වන්න අවශ්‍යමද?\",\n      \"leave\": \"ඉවත් වන්න\",\n      \"inviteUsersToRoomBtn\": \"පරිශීලකයින්ට සමූහයට ඇරයුම් කරන්න\",\n      \"invite\": \"ඇරයුම්\",\n      \"toggleMuteMicBtn\": \"microphone එකේ තත්වය වෙනස් කරන්න\",\n      \"mute\": \"නිශ්ශබ්ද කරන්න\",\n      \"unmute\": \"නිශ්ශබ්ද නොකරන්න\",\n      \"makeRoomPublicBtn\": \"පොදු සමූහයක් කරන්න!\",\n      \"settings\": \"සැකසුම්\",\n      \"speaker\": \"Speaker\",\n      \"listener\": \"Listener\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"ඔබගේ උපාංගය සහාය නොදක්වයි.ඔබට සැකසිය හැක\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"මම ඔබගේ උපාංගයට සහය දැක්වීමට උත්සහ කරන්නම්\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"ඇරයුම් කළා\",\n      \"inviteToRoom\": \"සමූහයට ඇරයුම් කරන්න\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"ඔබගේ mic එකට අවසර ලබාගැනීමේදී ප්‍රතික්ෂේප විය (ඔබගේ browser සැකසුම් වෙනස් කර පිටුව reload කරන්න)\",\n      \"dismiss\": \"මඟ හරින්න\",\n      \"tryAgain\": \"නැවත උත්සහ කරන්න\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"set keybind\",\n      \"listening\": \"listening\",\n      \"toggleMuteKeybind\": \"keybind වල තත්වය වෙනස් කරන්න\",\n      \"togglePushToTalkKeybind\": \"push-to-talk keybind වල තත්වය වෙනස් කරන්න\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"no audio consumer for some reason\"\n    },\n    \"addToCalendar\": { \"add\": \"දින දර්ශනයට එක් කරන්න\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"නැවත සම්බන්ධ වෙන්න\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"පොදු\",\n        \"private\": \"පෞද්ගලික\",\n        \"roomName\": \"සමූහයේ නම\",\n        \"roomDescription\": \"සමූහයේ විස්තර\",\n        \"descriptionError\": \"උපරිම දිග 500\",\n        \"nameError\": \"අකුරු 2 හා 60 අතර විය යුතුය\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"නව සමූහයක් සෑදුවා\",\n        \"roomInviteFrom\": \"සමූහ ඇරයුම් පත\",\n        \"justStarted\": \"ඇරඹුනා පමණි\",\n        \"likeToJoin\": \", ඔබ සම්බන්ධ වන්න කැමතිද?\",\n        \"inviteReceived\": \"ඔබට ඇරයුම් කර ඇත\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"පරිශීලක නම භාවිතා කර ඇත\",\n        \"avatarUrlError\": \"අවලංගු පිංතූරයකි\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord පිංතූරයේ සබැඳිය\",\n        \"displayNameError\": \"අකුරු 2 හා 50 අතර \",\n        \"displayNameLabel\": \"පෙන්විය යුතු නම\",\n        \"usernameError\": \"අකුරු 4 හා 15 අතර විය යුතු අතර alphanumeric/underscore පමණක් භාවිත කල යුතුය\",\n        \"usernameLabel\": \"පරිශීලක නම\",\n        \"bioError\": \"උපරිම දිග අකුරු 160 විය යුතුය\",\n        \"bioLabel\": \"වතගොත\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"ඔබට මෙම පරිශීලකයා ඔබ ඉදිරියට සාදන සමූහවලට සම්බන්ධ වීම නැවැත්විය යුතුද?\",\n        \"blockUser\": \"පරිශීලකයා අවහිර කරන්න\",\n        \"makeMod\": \"උපපරිපාලක කෙනෙක් කරන්න\",\n        \"unmod\": \"උපපරිපාලක කෙනෙක් නොකරන්න\",\n        \"addAsSpeaker\": \"කතා කරන්නෙක් ලෙස ඇතුලත් කරන්න\",\n        \"moveToListener\": \"අසන්නෙක් ලෙස ඇතුල් කරන්න\",\n        \"banFromChat\": \"chat එකෙන් ඉවත් කරන්න\",\n        \"banFromRoom\": \"සමූහයෙන් ඉවත් කරන්න\",\n        \"goBackToListener\": \"අසන්නෙක් ලෙසට යන්න\",\n        \"deleteMessage\": \"පිළිතුර ඉවත් කරන්න\",\n        \"makeRoomCreator\": \"පරිපාලක කරන්න\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"කතා කරන්න අවසර ගන්න\",\n        \"makePublic\": \"සමූහය පොදු කරන්න\",\n        \"makePrivate\": \"සමූහය පෞද්ගලික කරන්න\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"වෙන්කර ඇති සමූහ\",\n      \"noneFound\": \"හමුවූයේ නැත\",\n      \"allRooms\": \"සියලු වෙන්කර ඇති සමූහයන්\",\n      \"myRooms\": \"මම වෙන්කර ඇති සමූහයන්\",\n      \"scheduleRoomHeader\": \"සමූහයක් වෙන් කරන්න\",\n      \"startRoom\": \"සමූහය පටන්ගන්න\",\n      \"modal\": {\n        \"needsFuture\": \"ඉදිරියේදී තිබිය යුතුය\",\n        \"roomName\": \"සමූහයේ නම\",\n        \"roomDescription\": \"විස්‌තර\",\n        \"minLength\": \"අඩුම දිග 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"ඔබව සමූහයෙන් ඉවත්කර ඇත\",\n      \"waitAlert\": \"තවත් පිළිතුරක් ලබාදීමට තත්පරයක් සිටිය යුතුය\",\n      \"search\": \"සොයන්න\",\n      \"searchResults\": \"සොයන ලද ප්‍රතිඵල\",\n      \"recent\": \"නිතර භාවිතා කල\",\n      \"sendMessage\": \"පිළිතුරක් යවන්න\",\n      \"whisper\": \"කොඳුරන්න\",\n      \"welcomeMessage\": \"chat එකට පිළිගන්නවා!\",\n      \"roomDescription\": \"සමූහයේ විස්තර\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sk/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"načítať viac\",\n    \"loading\": \"načítam...\",\n    \"noUsersFound\": \"nenájdený žiadny používatelia\",\n    \"ok\": \"ok\",\n    \"yes\": \"áno\",\n    \"no\": \"nie\",\n    \"cancel\": \"zrušiť\",\n    \"save\": \"uložiť\",\n    \"edit\": \"upraviť\",\n    \"delete\": \"zmazať\",\n    \"joinRoom\": \"pripojiť sa do miestnosti\",\n    \"copyLink\": \"skopírovať odkaz\",\n    \"copied\": \"skopírováné\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Spúštanie DogeHose-u bez prístupových oprávnení môže spôsobovať nečakané chyby\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Stíšený | DogeHouse\",\n    \"deafenedTitle\": \"Vypnutý zvuk | DogeHouse\",\n    \"dashboard\": \"Nástenka\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Pôvod\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Nahlásiť chybu\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"zabanovať\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Zoznam používateľov ktorých sleduješ a niesú v súkromnej miestnosti\",\n      \"currentRoom\": \"aktuálne v:\",\n      \"startPrivateRoom\": \"začať s nimi súkromnú miestnosť\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Sledovať\",\n      \"followingHim\": \"Sledovaný\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Vytvoriť miestnosť\",\n      \"refresh\": \"Obnoviť\",\n      \"editRoom\": \"Upraviť miestnosť\",\n      \"desktopAlert\": \"Stiahni si DogeHouse aplikáciu už dnes!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"miestnosť je fuč, vráť sa\",\n      \"shareRoomLink\": \"zdieľať odkaz miestnosti\",\n      \"inviteFollowers\": \"Môžeš pozvať svojich sledovateľov ktorí sú online:\",\n      \"whenFollowersOnline\": \"Tu sa zobrazia tvoji sledovatelia ak budú online\"\n    },\n    \"login\": {\n      \"headerText\": \"Vynášame hlasové konverzácie až na mesiac 🚀\",\n      \"featureText_1\": \"Tmavý mód\",\n      \"featureText_2\": \"Voľné príhlásenie\",\n      \"featureText_3\": \"Cross-Platformová podpora\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Textový chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Prihlásiť sa cez GitHub\",\n      \"loginTwitter\": \"Prihlásiť sa cez Twitter\",\n      \"createTestUser\": \"Vytvoriť testovacieho používateľa\",\n      \"loginDiscord\": \"Prihlásiť sa cez Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Odhlásiť sa\",\n      \"probablyLoading\": \"zrejme načítam...\",\n      \"voiceSettings\": \"otvoriť nastavenia hlasu\",\n      \"soundSettings\": \"otvoriť nastavenia zvuku\",\n      \"deleteAccount\": \"odstrániť účet\",\n      \"overlaySettings\": \"otvoriť nastavenia prekresľovania\",\n      \"couldNotFindUser\": \"Užívateľ nenájdený\",\n      \"privacySettings\": \"Nastavenia súkromia\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Uups! Táto stránka sa stratila v konverzácii.\",\n      \"goHomeMessage\": \"Žiadny strach. Môžeš sa\",\n      \"goHomeLinkText\": \"vrátiť domov\"\n    },\n    \"room\": {\n      \"speakers\": \"Rozprávači\",\n      \"requestingToSpeak\": \"Chce povolenie rozprávať\",\n      \"listeners\": \"Poslucháči\",\n      \"allowAll\": \"Povoliť rozprávať všetkým\",\n      \"allowAllConfirm\": \"Si si istý? Povolíš rozprávať všetkým {{count}} žiadateľom\"\n    },\n    \"searchUser\": { \"search\": \"hľadať...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Zvuky\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"upraviť profil\",\n      \"followsYou\": \"sleduje ťa\",\n      \"followers\": \"sledovateľov\",\n      \"following\": \"sledovaných\",\n      \"followHim\": \"sledovať\",\n      \"followingHim\": \"sledovaný\",\n      \"copyProfileUrl\": \"skopírovať url profilu\",\n      \"urlCopied\": \"URL skopírované do schránky\",\n      \"unfollow\": \"Prestať sledovať\",\n      \"about\": \"O používateľovi\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Nastavenia hlasu\",\n      \"mic\": \"mikrofón:\",\n      \"permissionError\": \"žiadne mikrofóny nenájdené, buď žiaden nie je zapojený alebo si neudelil stránke oprávnenie.\",\n      \"refresh\": \"obnoviť zoznam mikrofónov\",\n      \"volume\": \"hlasitosť:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Nesprávny názov aplikácie\",\n        \"label\": \"Vlož názov aplikácie\"\n      },\n      \"header\": \"Nastavenia prekresľovania\"\n    },\n    \"download\": {\n      \"starting\": \"Začínam sťahovať...\",\n      \"failed\": \"Automatické stihnutie zlyhalo, skús znova neskôr\",\n      \"visit_gh\": \"Pozrieť vydania na Githube\",\n      \"prompt\": \"Pre začatie sťahovania klikni na tlačidlo\",\n      \"download_now\": \"Stiahnuť teraz\",\n      \"download_for\": \"Stiahnuť pre %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Zabanovaný používatelia\",\n      \"unban\": \"odbanovať\",\n      \"noBans\": \"nikto zatiaľ nebol zabanovaný\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Opustiť aktuálnu miestnosť\",\n      \"confirmLeaveRoom\": \"Skutočne chceš opustiť miestnosť?\",\n      \"leave\": \"Opustiť\",\n      \"inviteUsersToRoomBtn\": \"Pozvať používateľov do miestnosti\",\n      \"invite\": \"Pozvať\",\n      \"toggleMuteMicBtn\": \"Prepnúť stíšenie mikrofónu\",\n      \"mute\": \"Stíšiť\",\n      \"unmute\": \"Hovoriť\",\n      \"makeRoomPublicBtn\": \"Zverejniť miestnosť!\",\n      \"settings\": \"Nastavenia\",\n      \"speaker\": \"Rozprávač\",\n      \"listener\": \"Poslúchač\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Prepnúť stíšenie zvuku\",\n      \"deafen\": \"Vypnúť zvuk\",\n      \"undeafen\": \"Zapnúť zvuk\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Tvoje zariadenie nie je podporované. Môžeš vytvoriť:\",\n      \"linkText\": \"Issue na GitHube\",\n      \"addSupport\": \"a my se pre tvoje zariadenie pokusíme pridať podporu.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"pozvaný\",\n      \"inviteToRoom\": \"pozvať do miestnosti\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Prístup k mikrofónu zamietnutý (budeš musieť zmeniť nastavenia prehliadača a obnoviť stránku)\",\n      \"dismiss\": \"zavrieť\",\n      \"tryAgain\": \"skúsiť znovu\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"nastaviť klávesovú skratku\",\n      \"listening\": \"počúvam\",\n      \"toggleMuteKeybind\": \"prepínač stíšenia\",\n      \"togglePushToTalkKeybind\": \"prepínač push-to-talk\",\n      \"toggleOverlayKeybind\": \"prepínač prekreslenia\",\n      \"toggleDeafKeybind\": \"prepinač stíšenia zvuku\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"z nejakého dôvodu nemáme žiadny zvuk\"\n    },\n    \"addToCalendar\": { \"add\": \"Pridať do kalendára\" },\n    \"wsKilled\": {\n      \"description\": \"Websocket ukončený zo strany serveru. To sa väčšinou stáva ak máš stránku otvorenú v ďalšej záložke.\",\n      \"reconnect\": \"obnoviť pripojenie\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"verejná\",\n        \"private\": \"súkromá\",\n        \"roomName\": \"názov miestnosti\",\n        \"roomDescription\": \"popis miestnosti\",\n        \"descriptionError\": \"maximálna dĺžka je 500 znakov\",\n        \"nameError\": \"počet znakov musí byť medzi 2-60\",\n        \"subtitle\": \"Vyplň nasledovné polia pre vytvorenie novej miestnosti\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Vytvorená nová miestnosť\",\n        \"roomInviteFrom\": \"Pozvánka do miestnosti od\",\n        \"justStarted\": \"Práve začali\",\n        \"likeToJoin\": \", chceš sa pripojiť?\",\n        \"inviteReceived\": \"bol si pozvaný do\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"používatelské meno je obsadené\",\n        \"avatarUrlError\": \"Nesprávny obrázok\",\n        \"avatarUrlLabel\": \"odkaz na Github/Twitter/Discord obrázok\",\n        \"displayNameError\": \"počet znakov musí byť medzi 2-50\",\n        \"displayNameLabel\": \"Verejná prezývka\",\n        \"usernameError\": \"počet znakov musí byť medzi 4-15, povolené sú len písmená, čísla a podčiarkovník\",\n        \"usernameLabel\": \"Používatelské meno\",\n        \"bioError\": \"maximálna dĺžka je 160 znakov\",\n        \"bioLabel\": \"O mne\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Si si istý, že chceš zabanovať tohto používateľa zo všetkých tvojich miestností?\",\n        \"blockUser\": \"zabanovať používateľa\",\n        \"makeMod\": \"udeliť moderátora\",\n        \"unmod\": \"odobrať moderátora\",\n        \"addAsSpeaker\": \"pridať ako rozprávača\",\n        \"moveToListener\": \"presunúť na poslucháča\",\n        \"banFromChat\": \"zabanovať z chatu\",\n        \"banFromRoom\": \"zabanovat z miestnosti\",\n        \"goBackToListener\": \"vrátiť sa k poslucháčovi\",\n        \"deleteMessage\": \"zmazať túto správu\",\n        \"makeRoomCreator\": \"udeliť správcu miestnosti\",\n        \"unBanFromChat\": \"odbanovať z chatu\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Poslucháči musia požiadať o povolenie rozprávať\",\n        \"makePublic\": \"zverejniť miestnosť\",\n        \"makePrivate\": \"zosúkromniť miestnosť\",\n        \"renamePublic\": \"Nastaviť názov verejnej miestnosti\",\n        \"renamePrivate\": \"Nastaviť názov súkromnej miestnosti\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Ľudia\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Aktuálne niesú online žiadny tvoji priatelia\",\n      \"showMore\": \"Ukázať viac\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Nadchádzajúce miestnosti\",\n      \"exploreMoreRooms\": \"Preskúmať viac miestností\"\n    },\n    \"search\": {\n      \"placeholder\": \"Vyhľadaj miestnosti, užívateľov alebo kategórie\",\n      \"placeholderShort\": \"Hľadať\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Jazyk\",\n      \"reportABug\": \"Nahlásiť chybu\",\n      \"useOldVersion\": \"Prepnúť na starú verziu\",\n      \"logOut\": {\n        \"button\": \"Odhlásiť sa\",\n        \"modalSubtitle\": \"Si si istý, že sa chceš odhlásiť?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debugovať Audio\",\n        \"stopDebugger\": \"Zastaviť Debugger\"\n      },\n      \"downloadApp\": \"Stiahnuť aplikáciu\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Naplánované miestnosti\",\n      \"noneFound\": \"žiadne nenájdené\",\n      \"allRooms\": \"všetky naplánované miestnosti\",\n      \"myRooms\": \"moje naplánované miestnosti\",\n      \"scheduleRoomHeader\": \"Naplánovať miestnosť\",\n      \"startRoom\": \"začať miestnosť\",\n      \"modal\": {\n        \"needsFuture\": \"musí byť v budúcnosti\",\n        \"roomName\": \"názov miestnosti\",\n        \"minLength\": \"minimálne 2 znaky\",\n        \"roomDescription\": \"Popis\"\n      },\n      \"tommorow\": \"ZAJTRA\",\n      \"today\": \"DNES\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Si si istý, že chceš zmazať túto naplánovanú miestnosť?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotikony už čoskoro]\",\n      \"bannedAlert\": \"Bol si zabanovaný z chatu\",\n      \"waitAlert\": \"Pre poslanie ďalšej správy musíš počkať sekundu\",\n      \"search\": \"Hľadať\",\n      \"searchResults\": \"Výsledky vyhľadávania\",\n      \"recent\": \"Často používané\",\n      \"sendMessage\": \"Odoslať správu\",\n      \"whisper\": \"Pošepkať\",\n      \"welcomeMessage\": \"Vitaj v chate!\",\n      \"roomDescription\": \"popis miestnosti\",\n      \"disabled\": \"chat miestnosti bol vypnutý\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Tankujeme raketu\",\n      \"takingOff\": \"Vzlietame\",\n      \"inSpace\": \"Vo vesmíre\",\n      \"approachingMoon\": \"Blížime sa k mesiacu\",\n      \"lunarDoge\": \"Lunárny doge\",\n      \"approachingSun\": \"Blížime sa k slnku\",\n      \"solarDoge\": \"Solárny doge\",\n      \"approachingGalaxy\": \"Blížime sa ku galaxii\",\n      \"galacticDoge\": \"Galaktický Doge\",\n      \"spottedLife\": \"Zahliadnutá planéta so životom\"\n    },\n    \"feed\": { \"yourFeed\": \"Tvoj feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sl/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Prikažite več\",\n    \"loading\": \"Nalaganje...\",\n    \"noUsersFound\": \"Noben uporabnik ni bil najden\",\n    \"ok\": \"Ok\",\n    \"yes\": \"Da\",\n    \"no\": \"Ne\",\n    \"cancel\": \"Prekličite\",\n    \"save\": \"Shranite\",\n    \"edit\": \"Uredite\",\n    \"delete\": \"Izbrišite\",\n    \"joinRoom\": \"Pridružite se sobi\",\n    \"copyLink\": \"Kopirajte povezavo\",\n    \"copied\": \"Kopirano\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Zagon DogeHouse brez dovoljenja za dostopnost lahko povzroči nezaželene napake\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Utišano | DogeHouse\",\n    \"deafenedTitle\": \"Utišano | DogeHouse\",\n    \"dashboard\": \"Nadzorna plošča\",\n    \"connectionTaken\": \"Povezava je prekinjena\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Začetek\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Prijavite napako\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Blokirajte\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Seznam uporabnikov, ki jim sledite in niso v zasebni sobi.\",\n      \"currentRoom\": \"Trenutno v:\",\n      \"startPrivateRoom\": \"Začnite zasebno sobo s tem uporabnikom\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"sledi\",\n      \"followingHim\": \"sledijo\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Ustvarite sobo\",\n      \"refresh\": \"Osvežite\",\n      \"editRoom\": \"Uredite sobo\",\n      \"desktopAlert\": \"Prenesite DogeHouse namizno aplikacijo danes!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Sobe ni bilo mogoče najti! Nazaj\",\n      \"shareRoomLink\": \"Delite povezavo do sobe\",\n      \"inviteFollowers\": \"Povabite lahko svoje sledilce, ki so trenutno prijavljeni:\",\n      \"whenFollowersOnline\": \"Ko bodo vaši sledilci prijavljeni, bodo prikazani tukaj.\"\n    },\n    \"login\": {\n      \"headerText\": \"Z glasovnimi pogovori na luno 🚀\",\n      \"featureText_1\": \"Temna Tema\",\n      \"featureText_2\": \"Neomejena prijava\",\n      \"featureText_3\": \"Večplatformska podpora\",\n      \"featureText_4\": \"Odprtokoden projekt\",\n      \"featureText_5\": \"Besedilni klepet\",\n      \"featureText_6\": \"Poganja ga Doge\",\n      \"loginGithub\": \"Prijava z GitHub računom\",\n      \"loginTwitter\": \"Prijava z Twitter računom\",\n      \"createTestUser\": \"Ustvari testnega uporabnika\",\n      \"loginDiscord\": \"Prijava z Discord računom\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Odjava\",\n      \"probablyLoading\": \"Verjetno se nalaga...\",\n      \"voiceSettings\": \"Nastavitve glasu\",\n      \"soundSettings\": \"Nastavitve zvoka\",\n      \"deleteAccount\": \"Izbrišite račun\",\n      \"overlaySettings\": \"odprite nastavitve za okna\",\n      \"couldNotFindUser\": \"Tega uporabnika žal ni bilo mogoče najti\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Ta stran se je v pogovoru izgubila.\",\n      \"goHomeMessage\": \"Ne skrbte. Lahko greste\",\n      \"goHomeLinkText\": \"Domov\"\n    },\n    \"room\": {\n      \"speakers\": \"Govorci\",\n      \"requestingToSpeak\": \"Zahtevajo dovoljenje za vklop zvoka\",\n      \"listeners\": \"Poslušalci\",\n      \"allowAll\": \"Dovoli vsem\",\n      \"allowAllConfirm\": \"Ste prepričani? To bo omogočilo vsem {{count}}, ki zahtevajo, da govorijo\"\n    },\n    \"searchUser\": { \"search\": \"Iskanje...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Zvoki\",\n      \"title\": \"Nastavitve zvoka\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Uredite profil\",\n      \"followsYou\": \"ti sledijo\",\n      \"followers\": \"Sledilci\",\n      \"following\": \"Sledene osebe\",\n      \"followHim\": \"Sledite tej osebi\",\n      \"followingHim\": \"Sledite tej osebi\",\n      \"copyProfileUrl\": \"kopirajte povezavo profile\",\n      \"urlCopied\": \"povezava kopirana v odložišče\",\n      \"unfollow\": \"Prenehajte slediti\",\n      \"about\": \"O\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Nastavitve zvoka\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Mikrofona ni bilo mogoče najti. Morda ni priključen ali pa stran nima potrebnih pravic.\",\n      \"refresh\": \"Osvežite seznam mikrofonov\",\n      \"volume\": \"Glasnost:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Neveljaven naslov aplikacije\",\n        \"label\": \"Vnesite naslov aplikacije\"\n      },\n      \"header\": \"Nastavitve Okna\"\n    },\n    \"download\": {\n      \"starting\": \"Pričenjamo prenos...\",\n      \"failed\": \"Ni bilo mogoče samodejno prenesti, poskusite znova pozneje\",\n      \"visit_gh\": \"Obiščite Github Releases\",\n      \"prompt\": \"Za začetek prenosa kliknite spodnji gumb\",\n      \"download_now\": \"Prenesite zdaj\",\n      \"download_for\": \"Prenesite za %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Blokirani Uporabniki\",\n      \"unban\": \"Odblokirajte\",\n      \"noBans\": \"Blokirali (še) niste nobenega uporabnika\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Zapustite trenutno sobo\",\n      \"confirmLeaveRoom\": \"Ali si prepričani, da želite oditi?\",\n      \"leave\": \"Zapustite\",\n      \"inviteUsersToRoomBtn\": \"Povabite uporabnike v sobo\",\n      \"invite\": \"Povabite\",\n      \"toggleMuteMicBtn\": \"Vklop/Izklop mikrofona\",\n      \"mute\": \"Izklopite zvok\",\n      \"unmute\": \"Vklopite zvok\",\n      \"makeRoomPublicBtn\": \"Naredite sobo javno!\",\n      \"settings\": \"Nastavitve\",\n      \"speaker\": \"Govorec\",\n      \"listener\": \"Poslušalec\",\n      \"chat\": \"Klepet\",\n      \"toggleDeafMicBtn\": \"Vklop/Izklop zvoka\",\n      \"deafen\": \"Utišajte\",\n      \"undeafen\": \"Vklopite\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Vaša naprava trenutno ni podprta. Težavo lahko prijavite na\",\n      \"linkText\": \"GitHub\",\n      \"addSupport\": \"in poskusili bomo dodati podporo za vašo napravo.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"povabljeni\",\n      \"inviteToRoom\": \"povabite v sobo\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Nimamo pravic za uporabo vašega mikrofona. Odprite nastavitve brskalnika, omogočite dostop do mikrofona in nato osvežite stran.\",\n      \"dismiss\": \"Opustite\",\n      \"tryAgain\": \"Poskusite ponovno\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Nastavite bljižnico\",\n      \"listening\": \"Poslušajte\",\n      \"toggleMuteKeybind\": \"Preklopite bližnjico za vklop/izklop mikrofona\",\n      \"togglePushToTalkKeybind\": \"Preklopite bližnjico za pritisni-za-govor\",\n      \"toggleOverlayKeybind\": \"Preklopite bljižnjico za okno\",\n      \"toggleDeafKeybind\": \"Preklopite bljižnjico za izklop zvoka\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Iz nekega razloga ni odjemalca zvoka\"\n    },\n    \"addToCalendar\": { \"add\": \"Dodajte v koledar\" },\n    \"wsKilled\": {\n      \"description\": \"Povezava s spletno vtičnico je bila prekinjena. To se ponavadi zgodi, ko se ta spletna stran odpre v novem zavihku.\",\n      \"reconnect\": \"Ponovno se povežite\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Javno\",\n        \"private\": \"Zasebno\",\n        \"roomName\": \"Ime sobe\",\n        \"roomDescription\": \"Opis sobe\",\n        \"descriptionError\": \"največ 500 znakov\",\n        \"nameError\": \"mora biti dolžine 2 do 60 znakov\",\n        \"subtitle\": \"Izpolnite naslednja polja, da začneš novo sobo\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Nova soba ustvarjena\",\n        \"roomInviteFrom\": \"Povabilo od\",\n        \"justStarted\": \"So pravkar začeli\",\n        \"likeToJoin\": \", se želite pridružiti?\",\n        \"inviteReceived\": \"Povabljeni ste bili k\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Uporabniško ime zasedeno\",\n        \"avatarUrlError\": \"Neveljavna slika\",\n        \"avatarUrlLabel\": \"povezava do Github/Twitter/Discord slike\",\n        \"displayNameError\": \"Dolžina: 2 do 50 znakov\",\n        \"displayNameLabel\": \"Prikazno ime\",\n        \"usernameError\": \"Dolžina: 4 do 15 alfanumeričnih znakov ali podčrtaj\",\n        \"usernameLabel\": \"Uporabniško ime\",\n        \"bioError\": \"Največ 160 znakov\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"povezava do Twitter naslovne slike\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ste prepričani, da želite temu uporabniku onemogočiti dostop do vseh sob, ki jih boste ustvarili?\",\n        \"blockUser\": \"Blokirajte uporabnika\",\n        \"makeMod\": \"Dodajte kot moderatorja\",\n        \"unmod\": \"Odstranite moderatorja\",\n        \"addAsSpeaker\": \"Dodajte kot govorca\",\n        \"moveToListener\": \"Premaknite k poslušalcem\",\n        \"banFromChat\": \"Odstranite iz klepeta\",\n        \"banFromRoom\": \"Odstranite iz sobe\",\n        \"goBackToListener\": \"Nazaj k poslušalcem\",\n        \"deleteMessage\": \"Izbrišite sporočilo \",\n        \"makeRoomCreator\": \"Nastavite kot administratorja sobe\",\n        \"unBanFromChat\": \"Prekličite odstranitev iz klepeta\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Potrebno dovoljenje za govor\",\n        \"makePublic\": \"Naredite sobo javno\",\n        \"makePrivate\": \"Naredite sobo zasebno\",\n        \"renamePublic\": \"Nastavite javno ime sobe\",\n        \"renamePrivate\": \"Nastavite zasebno ime sobe\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Ljudje\",\n      \"online\": \"AKTIVNI\",\n      \"noOnline\": \"Trenutno ni noben prijatelj aktiven\",\n      \"showMore\": \"Pokažite več\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Prihajajoče\",\n      \"exploreMoreRooms\": \"Raziščite več sob\"\n    },\n    \"search\": {\n      \"placeholder\": \"Poiščite sobe, uporabnike ali kategorije\",\n      \"placeholderShort\": \"Iščite\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Jezik\",\n      \"reportABug\": \"Prijavite napako\",\n      \"useOldVersion\": \"Uporabite staro različico\",\n      \"logOut\": {\n        \"button\": \"Odjava\",\n        \"modalSubtitle\": \"Ali si prepričani, da se želiš odjaviti?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Odpravite napake zvoka\",\n        \"stopDebugger\": \"Ustavite razhroščevalec\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Načrtovane sobe\",\n      \"noneFound\": \"Ni zadetkov\",\n      \"allRooms\": \"Vse načrtovane sobe\",\n      \"myRooms\": \"Moje načrtovane sobe\",\n      \"scheduleRoomHeader\": \"Načrtujte sobo\",\n      \"startRoom\": \"Začnite sobo\",\n      \"modal\": {\n        \"needsFuture\": \"Mora biti v prihodnosti\",\n        \"roomName\": \"Ime sobe\",\n        \"roomDescription\": \"Opis\",\n        \"minLength\": \"Minimalno 2 znaka\"\n      },\n      \"tommorow\": \"JUTRI\",\n      \"today\": \"DANES\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Ali ste prepričani, da želite izbrisati načrtovano sobo?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Pogovor\",\n      \"emotesSoon\": \"[čustveni simboli - kmalu]\",\n      \"bannedAlert\": \"Bil ste odstranjeni iz klepeta\",\n      \"waitAlert\": \"Pred pošiljanjem novega sporočila morate počakati sekundo.\",\n      \"search\": \"Iskanje\",\n      \"searchResults\": \"Rezultati iskanja\",\n      \"recent\": \"Nedavno uporabljeno\",\n      \"sendMessage\": \"Pošljite sporočilo\",\n      \"whisper\": \"Šepetajte\",\n      \"welcomeMessage\": \"Pozdravljeni v klepetu!\",\n      \"roomDescription\": \"Opis sobe\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Natakanje goriva v raketo\",\n      \"takingOff\": \"Vzlet\",\n      \"inSpace\": \"V vesolju\",\n      \"approachingMoon\": \"Bližanje luni\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Bližanje soncu\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Bližanje galaksiji\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Najden planet z življenjem\"\n    },\n    \"feed\": { \"yourFeed\": \"Vaši Kanali\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/so/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"load more\",\n    \"loading\": \"loading...\",\n    \"noUsersFound\": \"lama helin isticmaaleyaal\",\n    \"ok\": \"hagaag\",\n    \"yes\": \"haa\",\n    \"no\": \"maya\",\n    \"cancel\": \"baaji\",\n    \"save\": \"kaydi\",\n    \"edit\": \"wax ka beddel\",\n    \"delete\": \"tirtir\",\n    \"joinRoom\": \"qolka ku biir\",\n    \"copyLink\": \"naqli xiriiriyaha\",\n    \"copied\": \"naqlay\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Asalka Sheekada\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Soo gudbi cillad\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"mamnuuc\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Liiska isticmaaleyaasha aan ku jirin qol gaar ah oo aad raacday.\",\n      \"currentRoom\": \"hadda ku jira:\",\n      \"startPrivateRoom\": \"qol gaar ah la bilow\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Abuur qol\",\n      \"refresh\": \"Cusbooneysii\",\n      \"editRoom\": \"Waxka bedel qolka\",\n      \"desktopAlert\": \"Dajiso desktop appka DogeHouse maanta?!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Qolka wuu dhamaday, dib u noqo\",\n      \"shareRoomLink\": \"Lawadaag xiriiriyaha qolkan\",\n      \"inviteFollowers\": \"Waad ku martiqaadi kartaa kuwa ku raacsan ee ku jira khadka:\",\n      \"whenFollowersOnline\": \"Markay kuwa ku raacsan ku jiraan khadka, halkan ayey kasoo muuqadaan.\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"ku gal GitHub\",\n      \"loginTwitter\": \"ku gal Twitter\",\n      \"createTestUser\": \"Samee koonto tijaabo ah\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"ka bax\",\n      \"probablyLoading\": \"probably loading...\",\n      \"voiceSettings\": \"tag goobta cod hagaajinta\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"soundSettings\": \"tag goobta dhawaq hagaajinta\",\n      \"deleteAccount\": \"tirtir koontada\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\n      \"goHomeMessage\": \"Ha walwalin. Waad awoodaa\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"room\": {\n      \"speakers\": \"Hadleyaal\",\n      \"requestingToSpeak\": \"Requesting to speak\",\n      \"listeners\": \"Dhageystayaal\",\n      \"allowAll\": \"Dhammaan oggolow\",\n      \"allowAllConfirm\": \"Ma hubtaa? Tani waxay u oggolaan doontaa dhammaan {{count}} Isticmaalayaasha inay codsadaan inay hadlaan\"\n    },\n    \"searchUser\": { \"search\": \"raadi...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Codka\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"wax ka beddel koontada\",\n      \"followsYou\": \"adiga kuraacay\",\n      \"followers\": \"raacsan\",\n      \"following\": \"raacay\",\n      \"followHim\": \"raac\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"naqli xiriiriyaha kontada\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"hagaajinta codka\",\n      \"mic\": \"codbaahiye:\",\n      \"permissionError\": \"Codbaahiye lama helin, kuugu ma xirno codbaahiye ama ogolasho umaaad siin websitka codbaahin.\",\n      \"refresh\": \"Cusbooneysii liiska cod baahiyeyaasha\",\n      \"volume\": \"volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Isticmaalayaasha la Mamnuucay\",\n      \"unban\": \"Ka qaad mamnuucida\",\n      \"noBans\": \"cidina wali lama mamnuucin\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Ka bax qolkaan \",\n      \"confirmLeaveRoom\": \"Ma hubtaa inaad rabtid inaad kabaxdo?\",\n      \"leave\": \"ka Bax\",\n      \"inviteUsersToRoomBtn\": \"Ku martiqaad isticmaalayaal qolka\",\n      \"invite\": \"Martiqaad\",\n      \"toggleMuteMicBtn\": \"Toggle mute microphone\",\n      \"mute\": \"Aamusi\",\n      \"unmute\": \"Codka u fur\",\n      \"makeRoomPublicBtn\": \"Qolka ka dhig qol guud!\",\n      \"settings\": \"Settings\",\n      \"speaker\": \"Hadle\",\n      \"listener\": \"Dhageyste\",\n      \"chat\": \"Wadahadal\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Ma taagerayo devicekaga. Waad abuuri kartaa\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"waxaan isku dayi doonaa inaan ku daro taageerida devicekaga.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"casuumay\",\n      \"inviteToRoom\": \"qolka ku casuum\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Oggolaanshaha waa ladiiday isku day inaad gaarikarto mikrafoonkaaga (Waxaa laga yaabaa inaad u baahato inaad gasho settinka biraawsarkaaga oo aad dib u cusboonaysiiso bogga)\",\n      \"dismiss\": \"Diid\",\n      \"tryAgain\": \"mar kale isku day\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"set keybind\",\n      \"listening\": \"dhagaysi\",\n      \"toggleMuteKeybind\": \"toggle mute keybind\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"toggle push-to-talk keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"no audio consumer for some reason\"\n    },\n    \"addToCalendar\": { \"add\": \"Ku dar Kalendarkaga\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"dib ugu xirmo\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Guud\",\n        \"private\": \"gaar \",\n        \"roomName\": \"Magaca qolka\",\n        \"roomDescription\": \"Qeexitaanka qolka\",\n        \"descriptionError\": \"Ugu badanaan 500\",\n        \"nameError\": \"waa inuu u dhexeeyaa 2 ilaa 60 xarfo\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Qol Cusub Ayaa La abuuray\",\n        \"roomInviteFrom\": \"Waxaad laga casuumay qolka\",\n        \"justStarted\": \"Hada ayey bilaabeen\",\n        \"likeToJoin\": \", ma jeceshahay inaad ku soo biirto?\",\n        \"inviteReceived\": \"waa lagugu casuumay\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"magaca isticmaalahan waa la qaatay\",\n        \"avatarUrlError\": \"Sawir Khaldan\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"2 ilaa 50 xaraf\",\n        \"displayNameLabel\": \"magaca bandhigmaya\",\n        \"usernameError\": \"4 ilaa 15 xaraf oo ah alphanumeric/underscore kaliya\",\n        \"usernameLabel\": \"Magaca isticmaalaha\",\n        \"bioError\": \"ugu badnaan 160 xaraf\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Mahubtaa inaad ka mamnuucdo isticmaalaha qolkasta oo aad abuurtid?\",\n        \"blockUser\": \"xanib isticmaalaha\",\n        \"makeMod\": \"make mod\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"Ku dar hadleyaasha\",\n        \"moveToListener\": \"ku celi Dhageystayaasha\",\n        \"banFromChat\": \"ka mamnuuc hadalka\",\n        \"banFromRoom\": \"ka mamnuuc qolka\",\n        \"goBackToListener\": \"Ku laabo Dhageystayaasha\",\n        \"deleteMessage\": \"tirtir fariintan\",\n        \"makeRoomCreator\": \"samee maamulaha qolka\",\n        \"unBanFromChat\": \"Ka qaad mamnuucida hadalka\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Waxay u baahnaan doonaan ogolaansho si ay uhadlaan\",\n        \"makePublic\": \"qolka ka dhig qol guud\",\n        \"makePrivate\": \"qolka ka dhig qol gaar\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"qolalka soo socda\",\n      \"exploreMoreRooms\": \"Sahmi qolal badan\"\n    },\n    \"search\": {\n      \"placeholder\": \"Raadi qolal, isticmaalayaal ama qeybo\",\n      \"placeholderShort\": \"Raadi\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"luuqadda\",\n      \"reportABug\": \"Soo gudbi cillad\",\n      \"useOldVersion\": \"Isticmaal Version hore\",\n      \"logOut\": {\n        \"button\": \"Ka bax\",\n        \"modalSubtitle\": \"Ma hubtaa inaad ka baxayso?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Dajiso Appka\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Jadwalka qolalka\",\n      \"noneFound\": \"midna lama helin\",\n      \"allRooms\": \"Dhamaan jadwalka qolalka\",\n      \"myRooms\": \"Jadwalka qolalkayga\",\n      \"scheduleRoomHeader\": \"usame jadwal qol\",\n      \"startRoom\": \"Bilaaw qol\",\n      \"modal\": {\n        \"needsFuture\": \"needs to be in the future\",\n        \"roomName\": \"magaca qolka\",\n        \"roomDescription\": \"qeexitaan\",\n        \"minLength\": \"ugu yaraan 2\"\n      },\n      \"tommorow\": \"BERRI\",\n      \"today\": \"MAANTA\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Ma hubtaa inaad tirtid jadwalka qolkaan?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"Waxaa lagaa mamnuucay wada hadalka\",\n      \"waitAlert\": \"Waa inaad sugto ilbiriqsi kahor intaadan dirin fariin kale\",\n      \"search\": \"Raadi\",\n      \"searchResults\": \"Raadi natiijooyin\",\n      \"recent\": \"badanaa la isticmaalo\",\n      \"sendMessage\": \"Dir Fariin\",\n      \"whisper\": \"fariin gaar ah\",\n      \"welcomeMessage\": \"Ku soo dhawow wada hadalka!\",\n      \"roomDescription\": \"qeexida qolka\",\n      \"disabled\": \"qolka sheekaysigiisa waa la joojiyay\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Ku dhowaanshaha dayaxa\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Ku dhowaanshaha cadceeda\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"meeraha laga helay nolasha\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sq/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"ngarko më tepër\",\n    \"loading\": \"duke ngarkuar...\",\n    \"noUsersFound\": \"no users found\",\n    \"ok\": \"ok\",\n    \"yes\": \"po\",\n    \"no\": \"jo\",\n    \"cancel\": \"anullo\",\n    \"save\": \"shpëto\",\n    \"edit\": \"redakto\",\n    \"delete\": \"fshij\",\n    \"joinRoom\": \"bashkohu dhomës\",\n    \"copyLink\": \"kopjo linkun\",\n    \"copied\": \"u kopjua\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"I heshtur | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Historia e origjinës\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Raporto problem\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"përjashto\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Lista e përdoruesve që nuk janë në një dhomë private dhe ti i ndjek.\",\n      \"currentRoom\": \"për momentin në:\",\n      \"startPrivateRoom\": \"fillo një dhomë private me ta\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Krijo dhomë\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"dhoma u zhduk, kthehu\",\n      \"shareRoomLink\": \"nda një link për te dhoma\",\n      \"inviteFollowers\": \"Mund të ftosh ndjekësit e tu që janë online:\",\n      \"whenFollowersOnline\": \"Kur ndjekësit e tu janë online, ata do të shaqen këtu lartë.\"\n    },\n    \"login\": {\n      \"headerText\": \"Sjellim bisedat me zë në hënë 🚀\",\n      \"featureText_1\": \"Tema e errët\",\n      \"featureText_2\": \"Hap regjistrimet\",\n      \"featureText_3\": \"Mështetje Cross-Platform\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Chat me tekst\",\n      \"featureText_6\": \"Mundësuar nga Doge\",\n      \"loginGithub\": \"hyrje me GitHub\",\n      \"loginTwitter\": \"hyrje me Twitter\",\n      \"createTestUser\": \"krijo pëdorues testi\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"dil\",\n      \"probablyLoading\": \"ndoshta duke u ngarkuar...\",\n      \"voiceSettings\": \"shko te cilësimet e zërit\",\n      \"soundSettings\": \"shko te cilësimet e tingullit\",\n      \"deleteAccount\": \"fshije llogarinë\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! Kjo faqja humbi në bisedë.\",\n      \"goHomeMessage\": \"Mos ki merak. Ti mundesh\",\n      \"goHomeLinkText\": \"për në shtëpi\"\n    },\n    \"room\": {\n      \"speakers\": \"Folësit\",\n      \"requestingToSpeak\": \"Duke kërkuar leje për të folur\",\n      \"listeners\": \"Dëgjuesit\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"kerko...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Tingujt\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"redakto profilin\",\n      \"followsYou\": \"ju ndjek\",\n      \"followers\": \"ndjekës\",\n      \"following\": \"ju ndjekin\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Cilësime e zërit\",\n      \"mic\": \"mikrofoni:\",\n      \"permissionError\": \"nuk u gjend asnjë mikrofon, ju ndoshta nuk e keni lidhur ose nuk i keni dhënë leje kesaj faqeje.\",\n      \"refresh\": \"rifresko listën e mikrofonëve\",\n      \"volume\": \"volumi:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Përdoruesit e përjashtuar\",\n      \"unban\": \"ç'përjashto\",\n      \"noBans\": \"askush nuk është përjashtuar derimë tani\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Largohu nga dhoma e tanishme\",\n      \"confirmLeaveRoom\": \"A jeni i sigurt që do të largohesh?\",\n      \"leave\": \"Largohu\",\n      \"inviteUsersToRoomBtn\": \"Fto përdorues në dhomë\",\n      \"invite\": \"Fto\",\n      \"toggleMuteMicBtn\": \"Hesht/ç'hesht mikrofonin\",\n      \"mute\": \"Hesht\",\n      \"unmute\": \"Ç'hesht\",\n      \"makeRoomPublicBtn\": \"Bëje dhomën publike!\",\n      \"settings\": \"Cilësimet\",\n      \"speaker\": \"Folësit\",\n      \"listener\": \"Dëgjuesit\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Paisja juaj nuk është e mbështetur. Ju mund të krijoni një\",\n      \"linkText\": \"çështje në GitHub\",\n      \"addSupport\": \"dhe unë do të mundohem të shtoj mbështetje për paisjen tuaj.\"\n    },\n    \"inviteButton\": { \"invited\": \"ftuar\", \"inviteToRoom\": \"fto në dhomë\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Leja për akses te mikrofoni nuk u dha (ju mund të shkoni në cilësimet e shfletuesit dhe ringarkoni faqen)\",\n      \"dismiss\": \"largo\",\n      \"tryAgain\": \"provo persëri\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"vendos keybind\",\n      \"listening\": \"duke dëgjuar\",\n      \"toggleMuteKeybind\": \"Keybind hesht/ç'hesht\",\n      \"togglePushToTalkKeybind\": \"Keybind ndiz/fik shtyp-për-të-folur\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"për ndonjë arsye nuk ka asnje konsumator audio\"\n    },\n    \"addToCalendar\": { \"add\": \"Shto në kalendar\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocketi u ndalua nga serveri. Kjo ndodh zakonisht kur hap një faqe të re në skedinë tjetër.\",\n      \"reconnect\": \"ri-lidhu\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"publike\",\n        \"private\": \"private\",\n        \"roomName\": \"emri dhomës\",\n        \"roomDescription\": \"përshkrimi i dhomës\",\n        \"descriptionError\": \"gjatësia maks. 500\",\n        \"nameError\": \"duhet të jetë midis 2 dhe 60 shkronjash\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Dhoma e re u krijua\",\n        \"roomInviteFrom\": \"Ftes dhome nga\",\n        \"justStarted\": \"Ata sapo filluan\",\n        \"likeToJoin\": \", a dëshironi të bashkoheni?\",\n        \"inviteReceived\": \"ju jeni ftuar në\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"emri i përdoruesit është i zënë\",\n        \"avatarUrlError\": \"Fotografi e pavlefshme\",\n        \"avatarUrlLabel\": \"url i avatarit të Github/Twitter/Discord\",\n        \"displayNameError\": \"gjatësia 2 deri 50 shkronja\",\n        \"displayNameLabel\": \"Emri i Shfaqur\",\n        \"usernameError\": \"gjatësia 4 deri 15 shkronja dhe vetëm alphanumerik/nevizim\",\n        \"usernameLabel\": \"Emri i përdoruesit\",\n        \"bioError\": \"gjatesia maks 160 shkronja\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"A jeni të sigurt që doni të bllokoni këtë perdoruest për t'iu bashkuar dhomave qe ju krijoni?\",\n        \"blockUser\": \"blloko përdorues\",\n        \"makeMod\": \"krijo mod\",\n        \"unmod\": \"ç'modo\",\n        \"addAsSpeaker\": \"shto si folës\",\n        \"moveToListener\": \"lëvize si dëgjues\",\n        \"banFromChat\": \"ndalo prej chatit\",\n        \"banFromRoom\": \"ndalo prej dhomës\",\n        \"goBackToListener\": \"kthenu në dëgjues\",\n        \"deleteMessage\": \"fshije këtë mesazh\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"kërko leje për të folur\",\n        \"makePublic\": \"bëje dhomën publike\",\n        \"makePrivate\": \"bëje dhomën private\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Dhomat e planifikuara\",\n      \"noneFound\": \"asnjë s'u gjend\",\n      \"allRooms\": \"të gjitha dhomat e planifikuara\",\n      \"myRooms\": \"dhomat e mija\",\n      \"scheduleRoomHeader\": \"Planifiko dhomë\",\n      \"startRoom\": \"fillo dhomën\",\n      \"modal\": {\n        \"needsFuture\": \"duhet të jetë në të ardhmen\",\n        \"roomName\": \"emri i dhomës\",\n        \"minLength\": \"gjatesia minimale 2\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes së shpejti]\",\n      \"bannedAlert\": \"Ju jeni ndaluar prej chatit\",\n      \"waitAlert\": \"Duhet të prisni një sekondë para se të dergoni një mesazh tjetër.\",\n      \"search\": \"Kërko\",\n      \"searchResults\": \"Kërko Rezultate\",\n      \"recent\": \"Përdorur shpesh\",\n      \"sendMessage\": \"Dërgo një mesazh\",\n      \"whisper\": \"Përshpërit\",\n      \"welcomeMessage\": \"Mirë se erdhët në chat!\",\n      \"roomDescription\": \"përshkrimi i dhomës\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sr/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Учитајте још\",\n    \"loading\": \"Учитавање...\",\n    \"noUsersFound\": \"Нема пронађених корисника\",\n    \"ok\": \"ok\",\n    \"yes\": \"да\",\n    \"no\": \"не\",\n    \"cancel\": \"откажи\",\n    \"save\": \"сачувајте\",\n    \"edit\": \"уредите\",\n    \"delete\": \"обришите\",\n    \"joinRoom\": \"придружите се соби\",\n    \"copyLink\": \"копирајте link\",\n    \"copied\": \"копирано\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Пригушен | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Прича о пореклу\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Пријавите проблем\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"забраните\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Корисници који нису у приватној соби и које пратите.\",\n      \"currentRoom\": \"тренутно у:\",\n      \"startPrivateRoom\": \"Започните приватну собу\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Направите собу\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Соба је нестала, вратите се назад\",\n      \"shareRoomLink\": \"Поделите везу до собе\",\n      \"inviteFollowers\": \"Можете позвати своје пратиоце који су активни:\",\n      \"whenFollowersOnline\": \"Када су Ваши пратиоци активни, они ће се појавити овде.\"\n    },\n    \"login\": {\n      \"headerText\": \"Доводимо гласовне конверзације до Месеца 🚀\",\n      \"featureText_1\": \"Тамна тема\",\n      \"featureText_2\": \"Отворене пријаве\",\n      \"featureText_3\": \"Подршка за више платформи\",\n      \"featureText_4\": \"Отвореног кода\",\n      \"featureText_5\": \"Текстуално ћаскање\",\n      \"featureText_6\": \"Покреће Doge\",\n      \"loginGithub\": \"Пријавите се путем GitHub-a\",\n      \"loginTwitter\": \"Пријавите се путем Twitter-a\",\n      \"createTestUser\": \"Креирајте тест корисника\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Одјавите се\",\n      \"probablyLoading\": \"вјероватно учитава...\",\n      \"voiceSettings\": \"идите на подешавања гласа\",\n      \"soundSettings\": \"идите на подешавања звука\",\n      \"deleteAccount\": \"Обришите налог\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Упс! Ова страница се изгубила у разговору.\",\n      \"goHomeMessage\": \"Без бриге. Можете да идете\",\n      \"goHomeLinkText\": \"на почетак\"\n    },\n    \"room\": {\n      \"speakers\": \"Говорници\",\n      \"requestingToSpeak\": \"Молим за говор\",\n      \"listeners\": \"Слушаоци\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"Претражите...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Звукови\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Уредите профил\",\n      \"followsYou\": \"Прати вас\",\n      \"followers\": \"Пратиоци\",\n      \"following\": \"Праћења\",\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Подешавања гласа\",\n      \"mic\": \"микрофон:\",\n      \"permissionError\": \"Није пронађен ниједан микрофон, или га нисте прикључили, или нисте дали дозволу овој веб локацији.\",\n      \"refresh\": \"Освежи листу микрофона\",\n      \"volume\": \"гласноћа:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Забрањени корисници\",\n      \"unban\": \"Поништите забрану\",\n      \"noBans\": \"Још нико није забрањен\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Напустите тренутну собу\",\n      \"confirmLeaveRoom\": \"Да ли сте сигурни да желите да напустите?\",\n      \"leave\": \"Напусти\",\n      \"inviteUsersToRoomBtn\": \"Позовите кориснике у собу\",\n      \"invite\": \"Позови\",\n      \"toggleMuteMicBtn\": \"Укључи / искључи микрофон\",\n      \"mute\": \"Искључите звук\",\n      \"unmute\": \"Укључите звук\",\n      \"makeRoomPublicBtn\": \"Учините собу јавном!\",\n      \"settings\": \"Подешавања\",\n      \"speaker\": \"Говорник\",\n      \"listener\": \"Слушалац\",\n      \"chat\": \"Ћаскање\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Ваш уређај тренутно није подржан. Можете направити\",\n      \"linkText\": \"пријаву на GitHub-у\",\n      \"addSupport\": \"и покушаћу да додам подршку за Ваш уређај.\"\n    },\n    \"inviteButton\": { \"invited\": \"Позван\", \"inviteToRoom\": \"Позван у собу\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Дозвола одбијена за покушај приступа микрофону (можда ћете морати да уђете у подешавања прегледача и поново учитате страницу)\",\n      \"dismiss\": \"Одбаците\",\n      \"tryAgain\": \"Покушајте поново\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Подесите пречицу на тастатури\",\n      \"listening\": \"Слушање\",\n      \"toggleMuteKeybind\": \"Укључите/искључите пречицу на тастатури за мутирање\",\n      \"togglePushToTalkKeybind\": \"Укључите/искључите пречицу на тастатури за говор на притисак\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Из неког разлога нема потрошача звука\"\n    },\n    \"addToCalendar\": { \"add\": \"Додајте у календар\" },\n    \"wsKilled\": {\n      \"description\": \"Websocket је убио сервер. То се обично дешава када отворите веб локацију на другој картици.\",\n      \"reconnect\": \"Поново повежите\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"јавно\",\n        \"private\": \"приватно\",\n        \"roomName\": \"назив собе\",\n        \"roomDescription\": \"опис собе\",\n        \"descriptionError\": \"максимална дужина 500\",\n        \"nameError\": \"мора бити између 2 до 60 карактера дужине\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Нова соба је направљена\",\n        \"roomInviteFrom\": \"Позив у собу од\",\n        \"justStarted\": \"Управо су почели\",\n        \"likeToJoin\": \", желите ли се придружити?\",\n        \"inviteReceived\": \"Позвани сте у\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Корисничко име је заузето\",\n        \"avatarUrlError\": \"Неважећа слика\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord аватар url\",\n        \"displayNameError\": \"дужина између 2 до 50 карактера\",\n        \"displayNameLabel\": \"Приказано име\",\n        \"usernameError\": \"дужина између 4 до 15 карактера и само слова, бројеви и доња црта\",\n        \"usernameLabel\": \"Корисничко име\",\n        \"bioError\": \"максимална дужина 160 карактера\",\n        \"bioLabel\": \"Опис\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Да ли сте сигурни да желите да блокирате овог корисника да се придружи било којој соби коју сте икада створили?\",\n        \"blockUser\": \"Блокирајте корисника\",\n        \"makeMod\": \"Додајте модератора\",\n        \"unmod\": \"Уклоните модератора\",\n        \"addAsSpeaker\": \"Додати као говорника\",\n        \"moveToListener\": \"Пређите на слушаоца\",\n        \"banFromChat\": \"Забраните приступ ћаскању\",\n        \"banFromRoom\": \"Забраните приступ соби\",\n        \"goBackToListener\": \"Вратите се слушаоцу\",\n        \"deleteMessage\": \"Обришите ову поруку\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Затражи дозволу за причање\",\n        \"makePublic\": \"Учините собу јавном\",\n        \"makePrivate\": \"Учините собу приватном\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Заказане собе\",\n      \"noneFound\": \"Ниједна није пронађена\",\n      \"allRooms\": \"Све заказане собе\",\n      \"myRooms\": \"Моје заказане собе\",\n      \"scheduleRoomHeader\": \"Закажите собу\",\n      \"startRoom\": \"Започните собу\",\n      \"modal\": {\n        \"needsFuture\": \"Треба да буде у будућности\",\n        \"roomName\": \"Назив собе\",\n        \"minLength\": \"Минимална дужина 2\",\n        \"roomDescription\": \"Description\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Ћаскање\",\n      \"emotesSoon\": \"[емотикони ускоро]\",\n      \"bannedAlert\": \"Забрањено вам је ћаскање\",\n      \"waitAlert\": \"Пре слања нове поруке морате сачекати секунду\",\n      \"search\": \"Претражите\",\n      \"searchResults\": \"Резултати претраге\",\n      \"recent\": \"Често коришћено\",\n      \"sendMessage\": \"Пошаљите поруку\",\n      \"whisper\": \"Шапћите\",\n      \"welcomeMessage\": \"Добро дошли у ћаскање!\",\n      \"roomDescription\": \"Опис собе\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sr-LATIN/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"učitaj još\",\n    \"loading\": \"učitavanje...\",\n    \"noUsersFound\": \"nema pronađenih korisnika\",\n    \"ok\": \"ok\",\n    \"yes\": \"da\",\n    \"no\": \"ne\",\n    \"cancel\": \"otkaži\",\n    \"save\": \"sačuvaj\",\n    \"edit\": \"uredi\",\n    \"delete\": \"obriši\",\n    \"joinRoom\": \"pridruži se sobi\",\n    \"copyLink\": \"kopiraj link\",\n    \"copied\": \"kopirano\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Prigušen | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Priča o poreklu\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Prijavi problem\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"zabrani\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Spisak korisnika koji nisu u privatnoj sobi i koje pratite.\",\n      \"currentRoom\": \"trenutno u:\",\n      \"startPrivateRoom\": \"započni privatnu sobu sa njima\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Napraviti sobu\",\n      \"refresh\": \"Osvježi\",\n      \"editRoom\": \"Urediti Sobu\",\n      \"desktopAlert\": \"Skinuti Dogehouse za desktop danas!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"soba je nestala, vrati me nazad\",\n      \"shareRoomLink\": \"podeli vezu do sobe\",\n      \"inviteFollowers\": \"Možete pozvati svoje pratioce koji su aktivni:\",\n      \"whenFollowersOnline\": \"Kada su vaši pratioci aktivni, oni će se pojaviti ovde.\"\n    },\n    \"login\": {\n      \"headerText\": \"Dovodimo glasovne konverzacije do Meseca 🚀\",\n      \"featureText_1\": \"Tamna tema\",\n      \"featureText_2\": \"Otvorene prijave\",\n      \"featureText_3\": \"Podrška za više platformi\",\n      \"featureText_4\": \"Otvorenog koda\",\n      \"featureText_5\": \"Tekstualno ćaskanje\",\n      \"featureText_6\": \"Pokreće Doge\",\n      \"loginGithub\": \"prijavi se preko GitHub-a\",\n      \"loginTwitter\": \"prijavi se preko Twitter-a\",\n      \"createTestUser\": \"Napraviti test korisnika\",\n      \"loginDiscord\": \"Prijavite se preko Discord-a\"\n    },\n    \"myProfile\": {\n      \"logout\": \"odjavi me\",\n      \"probablyLoading\": \"verovatno učitava...\",\n      \"voiceSettings\": \"idi na podešavanje glasa\",\n      \"soundSettings\": \"idi na podešavanje zvuka\",\n      \"deleteAccount\": \"obriši nalog\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Izvini, nismo pronašli ovog korisnika\",\n      \"privacySettings\": \"Podešavanja privatnosti\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Ups! Ova stranica se izgubila u razgovoru.\",\n      \"goHomeMessage\": \"Bez brige. Možete.\",\n      \"goHomeLinkText\": \"idi na početnu\"\n    },\n    \"room\": {\n      \"speakers\": \"Govornici\",\n      \"requestingToSpeak\": \"Molim za reč\",\n      \"listeners\": \"Slušaoci\",\n      \"allowAll\": \"Prihvati sve\",\n      \"allowAllConfirm\": \"Jesili siguran? Ovo če dopusti svi {{count}} pitajući useri de pričau\"\n    },\n    \"searchUser\": { \"search\": \"pretraži...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Zvukovi\",\n      \"title\": \"Zvukovi Podešavanja\",\n      \"playSound\": \"Zazvoni Zvuk\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"uredi profil\",\n      \"followsYou\": \"prati vas\",\n      \"followers\": \"pratioci\",\n      \"following\": \"praćenja\",\n      \"followHim\": \"prati\",\n      \"followingHim\": \"prateći\",\n      \"copyProfileUrl\": \"kopiraj profil url\",\n      \"urlCopied\": \"URL je kopiran na clipboard\",\n      \"unfollow\": \"Zapusti prateci\",\n      \"about\": \"Opis\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Sobe\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Poslaj DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"Ovaj korisnik je blokirao tebe\",\n        \"default\": \"Ups! Nismo mogli da učitamo ovog korisnik\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Podešavanja glasa\",\n      \"mic\": \"mikrofon:\",\n      \"permissionError\": \"nije pronađen nijedan mikrofon, ili ga niste priključili ili niste dali dozvolu ovoj web lokaciji.\",\n      \"refresh\": \"osveži listu mikrofona\",\n      \"volume\": \"jačina zvuka:\",\n      \"title\": \"Zvukovi Podešavanja\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Počinje skinu...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Vidi GitHub-a izdanja\",\n      \"prompt\": \"Klikni na ovaj drugme da počnes da skineš\",\n      \"download_now\": \"Skinuti Sad!\",\n      \"download_for\": \"Skinuti za %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Podešavanja privatnosti\",\n      \"header\": \"Podešavanja privatnosti\",\n      \"whispers\": { \"label\": \"Whispers\", \"uključite\": \"Uključite\", \"isključi\": \"Isključi\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Vaši Botovi\",\n      \"bots\": \"Botovi\",\n      \"title\": \"Bot informacije\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Klikni da vidiš ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Blokirani korisnici\",\n      \"unban\": \"Poništi blokadu\",\n      \"noBans\": \"još niko nije blokiran\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Napusti trenutnu sobu\",\n      \"confirmLeaveRoom\": \"Da li ste sigurni da želite da napustite sobu?\",\n      \"leave\": \"Napusti\",\n      \"inviteUsersToRoomBtn\": \"Pozovi korisnike u sobu\",\n      \"invite\": \"Pozovi\",\n      \"toggleMuteMicBtn\": \"Uključi / isključi mikrofon\",\n      \"mute\": \"Isključi zvug\",\n      \"unmute\": \"Uključi zvuk\",\n      \"makeRoomPublicBtn\": \"Učini sobu javnom!\",\n      \"settings\": \"Podešavanja\",\n      \"speaker\": \"Govornik\",\n      \"listener\": \"Slušalac\",\n      \"chat\": \"Ćaskanje\",\n      \"toggleDeafMicBtn\": \"Mutiraj se\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Vaš uređaj ternutno nije podržan. Možete stvoriti\",\n      \"linkText\": \"greška na GitHub-u\",\n      \"addSupport\": \"i pokušaću da dodam podršku za Vaš uređaj.\"\n    },\n    \"inviteButton\": { \"invited\": \"pozvan\", \"inviteToRoom\": \"pozvan u sobu\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Dozvola odbijena za pokušaj pristupa mikrofonu (možda ćete morati da uđete u podešavanja pregledača i ponovo učitate stranicu)\",\n      \"dismiss\": \"odbaci\",\n      \"tryAgain\": \"pokušaj ponovo\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"podesi prečicu na tastaturi\",\n      \"listening\": \"slušanje\",\n      \"toggleMuteKeybind\": \"uključi/isključi prečicu na tastaturi za mutiranje\",\n      \"togglePushToTalkKeybind\": \"uključi/isključi prečicu na tastaturi za govor na pritisak\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"nema audio konzumera iz nekog razloga\"\n    },\n    \"addToCalendar\": { \"add\": \"Dodaj u kalendar\" },\n    \"wsKilled\": {\n      \"description\": \"Websocket je ubio server. To se obično dešava kada otvorite web lokaciju na drugoj kartici.\",\n      \"reconnect\": \"ponovo poveži\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"javno\",\n        \"private\": \"privatno\",\n        \"roomName\": \"naziv sobe\",\n        \"roomDescription\": \"opis sobe\",\n        \"descriptionError\": \"maksimalna dužina 500\",\n        \"nameError\": \"mora biti od 2 do 60 karaktera dužine\",\n        \"subtitle\": \"Popuni sledeća polja da započneš novu sobu\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Kreirana nova soba\",\n        \"roomInviteFrom\": \"Poziv u novu sobu od\",\n        \"justStarted\": \"Upravo su počeli\",\n        \"likeToJoin\": \", želite li da se pridružite?\",\n        \"inviteReceived\": \"pozvani ste u\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"korisničko ime je zauzetno\",\n        \"avatarUrlError\": \"Nevažeća slika\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"dužina od 2 do 50 karaktera\",\n        \"displayNameLabel\": \"Prikazano ime\",\n        \"usernameError\": \"dužina od 4 do 15 karaktera i samo alfanumerički znakovi/donja crta\",\n        \"usernameLabel\": \"Korisničko ime\",\n        \"bioError\": \"maksimalna dužina 160 karaktera\",\n        \"bioLabel\": \"Opis\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Da li ste sigurni da želite da blokirate ovog korisnika da se pridruži bilo kojoj sobi koju ste ikada stvorili?\",\n        \"blockUser\": \"blokiraj korisnika\",\n        \"makeMod\": \"dodaj moderatora\",\n        \"unmod\": \"skloni moderatora\",\n        \"addAsSpeaker\": \"dodaj kao govornika\",\n        \"moveToListener\": \"pređi na slušaoca\",\n        \"banFromChat\": \"zabrani pristup ćaskanju\",\n        \"banFromRoom\": \"zabrani pristup sobi\",\n        \"goBackToListener\": \"vrati se slušaocu\",\n        \"deleteMessage\": \"obriši ovu poruku\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"zatraži dozvolu za govor\",\n        \"makePublic\": \"učini sobu javnom\",\n        \"makePrivate\": \"učini sobu privatnom\",\n        \"renamePublic\": \"zameni sobu ime\",\n        \"renamePrivate\": \"zameni privatnom sobu ime\",\n        \"chatDisabled\": \"Isključite govor u chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Uključen\",\n          \"disabled\": \"Isključen\",\n          \"followerOnly\": \"Samo sledbenici\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Ime je preuzeto\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Ljudi\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Nemaš ni jednog prijatelja online\",\n      \"showMore\": \"Prikaži više\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Nadolazeće sobe\",\n      \"exploreMoreRooms\": \"Pogledaj za više soba\"\n    },\n    \"search\": {\n      \"placeholder\": \"Pretraži sobe, korisnike ili kategorije\",\n      \"placeholderShort\": \"Pogledaj\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Jezik\",\n      \"reportABug\": \"Prijavi grešku\",\n      \"useOldVersion\": \"Korsiti staru verziju\",\n      \"logOut\": {\n        \"button\": \"Odjavi se\",\n        \"modalSubtitle\": \"Da li si siguran da želiš da se odjaviš?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debaguj Audio\",\n        \"stopDebugger\": \"Zaustavi Debager\"\n      },\n      \"downloadApp\": \"Skinuti App\",\n      \"developer\": \"Programar podešavanja\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Poruke\",\n      \"showMore\": \"Prikaži više\",\n      \"noMessages\": \"Nema nove poruke\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Zakazane sobe\",\n      \"noneFound\": \"nijedna nije pronađena\",\n      \"allRooms\": \"sve zakazane sobe\",\n      \"myRooms\": \"moje zakazane sobe\",\n      \"scheduleRoomHeader\": \"Zakaži sobu\",\n      \"startRoom\": \"započni sobu\",\n      \"modal\": {\n        \"needsFuture\": \"treba da bude u budućnosti\",\n        \"roomName\": \"naziv sobe\",\n        \"minLength\": \"minimalna dužina 2\",\n        \"roomDescription\": \"opis\"\n      },\n      \"tommorow\": \"SUTRA\",\n      \"today\": \"DANAS\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Jeseli ti sigurni da želite da izbrišete ovu zakazanu sobu?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Ćaskanje\",\n      \"emotesSoon\": \"[emotikoni uskoro]\",\n      \"bannedAlert\": \"Zabranjeno Vam je ćaskanje\",\n      \"waitAlert\": \"Pre slanja nove poruke morate da sačekate sekundu\",\n      \"search\": \"Pretraži\",\n      \"searchResults\": \"Rezultati pretrage\",\n      \"recent\": \"Često korišćeno\",\n      \"sendMessage\": \"Pošalji poruku\",\n      \"whisper\": \"šapći\",\n      \"welcomeMessage\": \"Dobrodošli u ćaskanje!\",\n      \"roomDescription\": \"opis sobe\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"poruku\",\n        \"retracted\": \"uvučen\",\n        \"deleted\": \"izbrisan\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Stavi se gorivo\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"U svemir\",\n      \"approachingMoon\": \"Približavanje meseca\",\n      \"lunarDoge\": \"Lunari doge\",\n      \"approachingSun\": \"Približavanje sunce\",\n      \"solarDoge\": \"Solarni doge\",\n      \"approachingGalaxy\": \"Približavanje galaksija\",\n      \"galacticDoge\": \"Galaktički Doge\",\n      \"spottedLife\": \"Uočena planeta sa životom\"\n    },\n    \"feed\": { \"yourFeed\": \"Tvoj Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/sv/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Visa fler\",\n    \"loading\": \"Laddar in...\",\n    \"noUsersFound\": \"Inga användare hittades\",\n    \"ok\": \"Okej\",\n    \"yes\": \"Ja\",\n    \"no\": \"Nej\",\n    \"cancel\": \"Avbryt\",\n    \"save\": \"Spara\",\n    \"edit\": \"Ändra\",\n    \"delete\": \"Radera\",\n    \"joinRoom\": \"Anslut till rummet\",\n    \"copyLink\": \"Kopiera länk\",\n    \"copied\": \"Kopierad\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Att använda DogeHouse utan att ha tillåtit webbläsaren att få åtkomst till vissa behörigheter kan orsaka oönskade fel.\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Tystad | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Bakgrund\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Rapportera ett fel\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"bannlys\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Personer du följer\",\n      \"currentRoom\": \"Ansluten till:\",\n      \"startPrivateRoom\": \"Skapa ett privat rum med dem\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Följ\",\n      \"followingHim\": \"Följer\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Skapa ett rum\",\n      \"refresh\": \"Uppdatera\",\n      \"editRoom\": \"Redigera rummet\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Rummet försvann under månförmörkelsen, gå tillbaka\",\n      \"shareRoomLink\": \"Dela länk till rummet\",\n      \"inviteFollowers\": \"Du kan bjuda in dina följare:\",\n      \"whenFollowersOnline\": \"När dina följare är uppkopplade kommer de att visas här.\"\n    },\n    \"login\": {\n      \"headerText\": \"Tar röstsamtal till månen 🚀\",\n      \"featureText_1\": \"Mörkt läge\",\n      \"featureText_2\": \"Öppna registreringar\",\n      \"featureText_3\": \"Plattformsoberoende\",\n      \"featureText_4\": \"Öppen källkod\",\n      \"featureText_5\": \"Textchatt\",\n      \"featureText_6\": \"Drivs av Doge\",\n      \"loginGithub\": \"Logga in med GitHub\",\n      \"loginTwitter\": \"Logga in med Twitter\",\n      \"createTestUser\": \"Skapa testanvändare\",\n      \"loginDiscord\": \"Logga in med Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Logga ut\",\n      \"probablyLoading\": \"Laddar...\",\n      \"voiceSettings\": \"Röstinställningar\",\n      \"soundSettings\": \"Ljudinställningar\",\n      \"deleteAccount\": \"Radera konto\",\n      \"overlaySettings\": \"Overlayinställningar\",\n      \"couldNotFindUser\": \"Förlåt, vi kunde inte hitta den användaren\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! Denna sida blev förlorad i konversationen.\",\n      \"goHomeMessage\": \"Ingen fara. Du kan\",\n      \"goHomeLinkText\": \"gå hem\"\n    },\n    \"room\": {\n      \"speakers\": \"Talare\",\n      \"requestingToSpeak\": \"Begär att få tala\",\n      \"listeners\": \"Lyssnare\",\n      \"allowAll\": \"Tillåt alla\",\n      \"allowAllConfirm\": \"Är du säker på detta sergeant? Detta kommer tillåta {{count}} användare att få tala. Det kan utplåna månen! 🚀☢️\"\n    },\n    \"searchUser\": { \"search\": \"Sök...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Ljud\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Redigera profil\",\n      \"followsYou\": \"Följer dig\",\n      \"followers\": \"Följare\",\n      \"following\": \"Följer\",\n      \"followHim\": \"Följ\",\n      \"followingHim\": \"Följer\",\n      \"copyProfileUrl\": \"Kopiera profillänk\",\n      \"urlCopied\": \"Länken har kopierats\",\n      \"unfollow\": \"Sluta följa\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Röstinställningar\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Ingen mikrofon hittades, du har antingen ingen inkopplad eller så har du inte gett hemsidan tillåtelse.\",\n      \"refresh\": \"Uppdatera mikrofonlistan\",\n      \"volume\": \"Volym:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlayinställningar\",\n      \"input\": {\n        \"errorMsg\": \"Ogiltlig fönstertitel\",\n        \"label\": \"Ange fönstertitel\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Påbörjar nedladdning...\",\n      \"failed\": \"Det gick inte att ladda ner automatiskt, försök igen senare\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Klicka på knappen nedan för att starta nedladdningen\",\n      \"download_now\": \"Ladda ner nu\",\n      \"download_for\": \"Ladda ner för %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bannlys användare\",\n      \"unban\": \"Ta bort bannlysning\",\n      \"noBans\": \"Ingen har blivit bannlyst ännu\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Lämna rummet\",\n      \"confirmLeaveRoom\": \"Är du säker på att du vill lämna?\",\n      \"leave\": \"Lämna\",\n      \"inviteUsersToRoomBtn\": \"Bjud in användare till rummet\",\n      \"invite\": \"Bjud in\",\n      \"toggleMuteMicBtn\": \"Stäng av mikrofonen\",\n      \"mute\": \"Tysta\",\n      \"unmute\": \"Slå på ljud\",\n      \"makeRoomPublicBtn\": \"Gör rum offentligt\",\n      \"settings\": \"Inställningar\",\n      \"speaker\": \"Talare\",\n      \"listener\": \"Lyssnare\",\n      \"chat\": \"Chatt\",\n      \"toggleDeafMicBtn\": \"Växla ljud\",\n      \"deafen\": \"Ljud av\",\n      \"undeafen\": \"Ljud på\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Åtkomst nekad när vi försökte att ansluta till din mikrofon (du kan behöva gå till webbläsarinställningarna, tillåta att webläsaren tillåter din mikrofon och sedan ladda om sidan)\",\n      \"dismiss\": \"Avfärda\",\n      \"tryAgain\": \"Försök igen\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Offentligt\",\n        \"private\": \"Privat\",\n        \"roomName\": \"Rumsnamn\",\n        \"roomDescription\": \"Rumsbeskrivning\",\n        \"descriptionError\": \"Max längden är 500 karaktärer\",\n        \"nameError\": \"Längden på namnet måste vara mellan 2 och 60 karaktärer\",\n        \"subtitle\": \"Fyll i dessa fält för att skapa ett rum\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Ett nytt rum har skapats\",\n        \"roomInviteFrom\": \"Inbjudan från\",\n        \"justStarted\": \"De har precis börjat\",\n        \"likeToJoin\": \", skulle du vilja ansluta?\",\n        \"inviteReceived\": \"Du har blivit inbjuden till\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Användarnamn är redan taget\",\n        \"avatarUrlError\": \"Ogiltig bild\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord profilbild länk\",\n        \"displayNameError\": \"Maximum 2 till 50 karaktärer\",\n        \"displayNameLabel\": \"Visningsnamn\",\n        \"usernameError\": \"Maximum 4 till 15 karaktärer med bara alfanumerisk/understräck\",\n        \"usernameLabel\": \"Användarnamn\",\n        \"bioError\": \"Maximum längd på 160 karaktärer\",\n        \"bioLabel\": \"Biografi\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Är du säker på att du vill blockera denna användare från att ansluta till dina rum?\",\n        \"blockUser\": \"Blockera användare\",\n        \"makeMod\": \"Gör moderator\",\n        \"unmod\": \"Ta bort moderator\",\n        \"addAsSpeaker\": \"Lägg till som talare\",\n        \"moveToListener\": \"Flytta till lyssnare\",\n        \"banFromChat\": \"Bannlys från chatt\",\n        \"banFromRoom\": \"Bannlys från rum\",\n        \"goBackToListener\": \"Gå tillbaka till lyssnare\",\n        \"deleteMessage\": \"Ta bort meddelande\",\n        \"makeRoomCreator\": \"Gör till rumsadministratör\",\n        \"unBanFromChat\": \"Ta bort avstängning från chatt\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Be om tillåtelse att prata\",\n        \"makePublic\": \"Gör rum offentligt\",\n        \"makePrivate\": \"Gör rum privat\",\n        \"renamePublic\": \"Ange ett offentligt rumsnamn\",\n        \"renamePrivate\": \"Ange ett privat rumsnamn\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"Ingen ljudanvändare av någon anledning\"\n    },\n    \"addToCalendar\": { \"add\": \"Spara i kalender\" },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Din enhet stöds inte. Du kan skapa ett\",\n      \"linkText\": \"issue på GitHub\",\n      \"addSupport\": \", så kommer jag försöka lägga till stöd för din enhet.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Inbjuden\",\n      \"inviteToRoom\": \"Bjud in till rum\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Ställ in kortkommando\",\n      \"listening\": \"Lyssnar\",\n      \"toggleMuteKeybind\": \"Växla tystad kortkommando\",\n      \"togglePushToTalkKeybind\": \"Växla tryck-för-tal kortkommando\",\n      \"toggleOverlayKeybind\": \"Växla overlay kortkommando\",\n      \"toggleDeafKeybind\": \"Växla tyst kortkommando\"\n    },\n    \"wsKilled\": {\n      \"description\": \"WebSocket anslutningen blev nerstängd av servern. Detta händer oftast när du öppnar hemsidan i en annan flik.\",\n      \"reconnect\": \"Återanslut\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Personer\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"Du har 0 vänner som är uppkopplade just nu\",\n      \"showMore\": \"Visa fler\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Kommande rum\",\n      \"exploreMoreRooms\": \"Utforska Fler Rum\"\n    },\n    \"search\": {\n      \"placeholder\": \"Sök efter rum, användare eller kategorier\",\n      \"placeholderShort\": \"Sök\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Språk\",\n      \"reportABug\": \"Rapportera ett fel\",\n      \"useOldVersion\": \"Använd förra versionen\",\n      \"logOut\": {\n        \"button\": \"Logga ut\",\n        \"modalSubtitle\": \"Är du säker på att du vill logga ut?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Felsök ljud\",\n        \"stopDebugger\": \"Stoppa felsökaren\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Schemalagda rum\",\n      \"noneFound\": \"Inga hittades\",\n      \"allRooms\": \"Alla schemalagda rum\",\n      \"myRooms\": \"Mina schemalagda rum\",\n      \"scheduleRoomHeader\": \"Schemalagda Rum\",\n      \"startRoom\": \"Påbörja rum\",\n      \"modal\": {\n        \"needsFuture\": \"Behöver vara i framtiden\",\n        \"roomName\": \"Namn\",\n        \"roomDescription\": \"Beskrivning\",\n        \"minLength\": \"Lägsta längden: 2\"\n      },\n      \"tommorow\": \"IMORGON\",\n      \"today\": \"IDAG\",\n      \"deleteModal\": {\n        \"areYouSure\": \"är du säker på att du vill ta bort det här schemalagda rummet?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chatt\",\n      \"emotesSoon\": \"[emotes kommer snart]\",\n      \"bannedAlert\": \"Du blev bannlyst från chatten\",\n      \"waitAlert\": \"Du behöver vänta en sekund innan du kan skicka fler meddelanden\",\n      \"search\": \"Sök\",\n      \"searchResults\": \"Sökresultat\",\n      \"recent\": \"Ofta använda\",\n      \"sendMessage\": \"Skicka ett meddelande\",\n      \"whisper\": \"Direktmeddelande\",\n      \"welcomeMessage\": \"Välkommen till chatten!\",\n      \"roomDescription\": \"Rumbeskrivning\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Tankar raket\",\n      \"takingOff\": \"Lyfter\",\n      \"inSpace\": \"I rymden\",\n      \"approachingMoon\": \"Närmar sig månen\",\n      \"lunarDoge\": \"Måndoge\",\n      \"approachingSun\": \"Närmar sig solen\",\n      \"solarDoge\": \"Soldoge\",\n      \"approachingGalaxy\": \"Närmar sig en galax\",\n      \"galacticDoge\": \"Galaktiskdoge\",\n      \"spottedLife\": \"Planet med liv upptäckt\"\n    },\n    \"feed\": { \"yourFeed\": \"Ditt flöde\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ta/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"மேலும் காட்டு\",\n    \"loading\": \"பதிவேறுகிறது, காத்திருக்கவும்...\",\n    \"noUsersFound\": \"பயனர்கள் இல்லை\",\n    \"ok\": \"சரி\",\n    \"yes\": \"ஆம்\",\n    \"no\": \"இல்லை\",\n    \"cancel\": \"ரத்துசெய்\",\n    \"save\": \"சேமி\",\n    \"edit\": \"திருத்து\",\n    \"delete\": \"அழி\",\n    \"joinRoom\": \"அறையில் சேர்\",\n    \"copyLink\": \"இணைப்பை நகலெடு\",\n    \"copied\": \"நகலெடுக்கப்பட்டது\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"குரல் முடக்கப்பட்டது  | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"மூல கதை\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"ஒரு bug'ஐ புகாரளிக்கவும்\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"தடை செய்\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"ஒரு தனியார் அறையில் இல்லாத, நீங்கள் பின்பற்றும் பயனர்களின்.\",\n      \"currentRoom\": \"தற்போது இருப்பவர்கள்:\",\n      \"startPrivateRoom\": \"அவர்களுடன் ஒரு தனியார் அறையைத் தொடங்குங்கள்\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"பின்தொடரவும்\",\n      \"followingHim\": \"இவர்களை பின்தொடர்கிறீர்கள்\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"அறையை உருவாக்குங்கள்\",\n      \"refresh\": \"புதுப்பிக்கவும்\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"அறை இல்லை, திரும்பிச் செல்லுங்கள்\",\n      \"shareRoomLink\": \"அறைக்கு இணைப்பைப் பகிரவும்\",\n      \"inviteFollowers\": \"ஆன்லைனில் இருக்கும் உங்களைப் பின்தொடர்பவர்களை அழைக்கலாம்:\",\n      \"whenFollowersOnline\": \"உங்களைப் பின்தொடர்பவர்கள் ஆன்லைனில் இருக்கும்போது, இங்கே காண்பிக்கப்படுவார்கள்.\"\n    },\n    \"login\": {\n      \"headerText\": \"உங்கள் குரலை நிலாவுக்கு பேக் செய்கிறோம் 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"எழுத்து அரட்டை\",\n      \"featureText_6\": \"இது Doge ஆல் பவர் செய்யப்பட்டது  \",\n      \"loginGithub\": \"GitHub மூலமாக உள்நுழை\",\n      \"loginTwitter\": \"Twitter மூலமாக உள்நுழை\",\n      \"createTestUser\": \"சோதனை பயனரை உருவாக்கவும்\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"லாக் அவுட் செய்\",\n      \"probablyLoading\": \"பதிவேறுகிறது என்று நினைக்கிறன்...\",\n      \"voiceSettings\": \"குரல் அமைப்பிற்குச் செல்லவும்\",\n      \"soundSettings\": \"ஒலி அமைப்பிற்குச் செல்லவும்\",\n      \"deleteAccount\": \"கணக்கை நீக்கு\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"அய்யகோ! இந்த பக்கத்தை காணவில்லை\",\n      \"goHomeMessage\": \"கவலை படாதே, \",\n      \"goHomeLinkText\": \"Home'இற்கு செல் \"\n    },\n    \"room\": {\n      \"speakers\": \"பேசுகின்றவர்கள்\",\n      \"requestingToSpeak\": \"பேசுவதற்கு கோரிக்கை வைக்கின்றேன்\",\n      \"listeners\": \"கவனிக்கின்றவர்கள்\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"தேடு...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"ஒலிகள்\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"சுயவிவரத்தைத் திருத்து\",\n      \"followsYou\": \"உங்களைப் பின்தொடர்கிறார்\",\n      \"followers\": \"உங்களைப் பின்தொடர்பவர்கள்\",\n      \"following\": \"நீங்கள் பின்தொடர்பவர்கள் \",\n      \"followHim\": \"இவரை பின்தொடர்\",\n      \"followingHim\": \"இவரை பிந்தொடர்கிறீர்கள்\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"unfollow\": \"Unfollow\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"குரல் அமைப்பு\",\n      \"mic\": \"ஒலிவாங்கி:\",\n      \"permissionError\": \"ஒலிவாங்கிகள் எதுவும் கிடைக்கவில்லை, நீங்கள் எதுவும் செருகவில்லை அல்லது இந்த வலைத்தளத்திற்கு அனுமதி வழங்கவில்லை.\",\n      \"refresh\": \"ஒலிவாங்கி பட்டியலைப் புதுப்பிக்கவும்\",\n      \"volume\": \"ஒலி அளவு:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"தடைசெய்யப்பட்ட பயனர்கள்\",\n      \"unban\": \"தடையை நீக்கு\",\n      \"noBans\": \"யாரும் இதுவரை தடை செய்யப்படவில்லை\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"தற்போதைய அறையை விட்டு விடுங்கள்\",\n      \"confirmLeaveRoom\": \"நீங்கள் நிச்சயமாக வெளியேற விரும்புகிறீர்களா?\",\n      \"leave\": \"வெளியேறு  \",\n      \"inviteUsersToRoomBtn\": \"பயனர்களை அறைக்கு அழைக்கவும்\",\n      \"invite\": \"அழை\",\n      \"toggleMuteMicBtn\": \"முடங்கிய மைக்ரோஃபோனை நிலைமாற்று\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"makeRoomPublicBtn\": \"அறையை பொதுவாக்கு!\",\n      \"settings\": \"அமைப்புகள்\",\n      \"speaker\": \"பேச்சாளர்\",\n      \"listener\": \"கேட்பவர்\",\n      \"chat\": \"அரட்டை\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"உங்கள் சாதனம் தற்போது ஆதரிக்கப்படவில்லை. நீங்கள்\",\n      \"linkText\": \"GitHub'ல் issue உருவாக்கவும்.\",\n      \"addSupport\": \"உங்கள் சாதனத்தில் ஆதரவைச் சேர்க்க முயற்சிக்கிறேன்\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"அழைக்கப்பட்டார்\",\n      \"inviteToRoom\": \"அறைக்கு அழைக்கவும்\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"உங்கள் மைக்கை அணுக முயற்சிக்க அனுமதி மறுக்கப்பட்டது (நீங்கள் browser அமைப்புகளுக்குச் சென்று பக்கத்தை மீண்டும் ஏற்ற வேண்டியிருக்கும்)\",\n      \"dismiss\": \"புறக்கணி\",\n      \"tryAgain\": \"மீண்டும் முயற்சி செய்\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"விசைப்பலகையை அமைக்கவும்\",\n      \"listening\": \"கேட்கிறேன்\",\n      \"toggleMuteKeybind\": \"toggle mute keybind\",\n      \"togglePushToTalkKeybind\": \"toggle push-to-talk keybind\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"சில காரணங்களால் ஆடியோ நுகர்வோர் இல்லை\"\n    },\n    \"addToCalendar\": { \"add\": \"காலெண்டரில் சேர்\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket server'ஆல் கொல்லப்பட்டது . நீங்கள் மற்றொரு தாவலில் வலைத்தளத்தைத் திறக்கும்போது இது வழக்கமாக நிகழ்கிறது.\",\n      \"reconnect\": \"மீண்டும் இணைக்கவும்\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"பொது\",\n        \"private\": \"தனியார் அறை\",\n        \"roomName\": \"அறையின் பெயர்\",\n        \"roomDescription\": \"அறையின் விளக்கம்\",\n        \"descriptionError\": \"அதிகபட்ச நீளம் 500\",\n        \"nameError\": \"2 முதல் 60 எழுத்துக்கள் வரை நீளமாக இருக்க வேண்டும்\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"புதிய அறை உருவாக்கப்பட்டது\",\n        \"roomInviteFrom\": \"இவர் உங்களை அறைக்கு அழைக்கிறார் \",\n        \"justStarted\": \"அறை சற்று நேரம் முன்னர் ஆரம்பிக்கப்பட்டது \",\n        \"likeToJoin\": \", நீங்கள் சேர விரும்புகிறீர்களா?\",\n        \"inviteReceived\": \"நீங்கள் அழைக்கப்பட்டுள்ளீர்கள்\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"பயனர்பெயர் கிடைக்கவில்லை\",\n        \"avatarUrlError\": \"இந்த படத்தைப் பயன்படுத்த முடியாது\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord பட url\",\n        \"displayNameError\": \"நீளம் 2 முதல் 50 எழுத்துக்கள்\",\n        \"displayNameLabel\": \"காட்டவேண்டிய பெயர் \",\n        \"usernameError\": \"நீளம் 4 முதல் 15 எழுத்துக்கள் மற்றும் எண்ணெழுத்து / அடிக்கோடும்\",\n        \"usernameLabel\": \"பயனர்பெயர்\",\n        \"bioError\": \"அதிகபட்சம் 160 எழுத்துகள்\",\n        \"bioLabel\": \"சுயசரிதை\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"நீங்கள் உருவாக்கிய எந்த அறையிலும் இந்த பயனரைச் சேர்ப்பதைத் தடுக்க விரும்புகிறீர்களா?\",\n        \"blockUser\": \"பயனரை தடை செய்\",\n        \"makeMod\": \"mod ஆக்குங்கள்\",\n        \"unmod\": \"unmod செய்யுங்கள்\",\n        \"addAsSpeaker\": \"பேச்சாளராக சேர்க்கவும்\",\n        \"moveToListener\": \"கேட்பவராக நகர்த்தவும்\",\n        \"banFromChat\": \"அரட்டையிலிருந்து தடைசெய்க\",\n        \"banFromRoom\": \"அறையில் இருந்து தடைசெய்க\",\n        \"goBackToListener\": \"கேட்பவராக திரும்பிச் செல்லுங்கள்\",\n        \"deleteMessage\": \"இந்த செய்தியை நீக்கு\",\n        \"makeRoomCreator\": \"அவரை அறையின் நிர்வாகியாக ஆக்கு\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"பேச அனுமதி தேவை\",\n        \"makePublic\": \"அறையை பொதுவாக்குங்கள்\",\n        \"makePrivate\": \"அறையை தனிப்பட்டதாக்குங்கள்\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"திட்டமிடப்பட்ட அறைகள்\",\n      \"noneFound\": \"எதுவும் கிடைக்கவில்லை\",\n      \"allRooms\": \"அனைத்து திட்டமிடப்பட்ட அறைகள்\",\n      \"myRooms\": \"எனது திட்டமிடப்பட்ட அறைகள்\",\n      \"scheduleRoomHeader\": \"அறையை பிற்காலத்துக்கு திட்டமிட்டு \",\n      \"startRoom\": \"அறையைத் தொடங்கு\",\n      \"modal\": {\n        \"needsFuture\": \"எதிர்காலத்தில் இருக்க வேண்டும்\",\n        \"roomName\": \"அறை பெயர்\",\n        \"roomDescription\": \"விளக்கம்\",\n        \"minLength\": \"குறைந்தபட்ச நீளம் 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"அரட்டை\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"நீங்கள் அரட்டையிலிருந்து தடைசெய்யப்பட்டீர்கள்\",\n      \"waitAlert\": \"மற்றொரு செய்தியை அனுப்புவதற்கு முன்பு நீங்கள் ஒரு நொடி காத்திருக்க வேண்டும்\",\n      \"search\": \"தேடு\",\n      \"searchResults\": \"தேடல் முடிவுகள்\",\n      \"recent\": \"அடிக்கடி பயன்படுத்தப்படுபவை\",\n      \"sendMessage\": \"ஒரு செய்தியை அனுப்புங்கள்\",\n      \"whisper\": \"கிசுகிசுக்கவும்\",\n      \"welcomeMessage\": \"அரட்டைக்கு வரவேற்கிறோம்!\",\n      \"roomDescription\": \"அறை விளக்கம்\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Rocket'க்கு petrol போடுகிறோம் \",\n      \"takingOff\": \"இப்போது புறப்படுகிறது\",\n      \"inSpace\": \"வானத்தில் இருக்கிறோம்\",\n      \"approachingMoon\": \"இன்னும் சற்று நேரத்தில் நிலவை அடைந்து விடுவோம்\",\n      \"lunarDoge\": \"நிலவில் Doge\",\n      \"approachingSun\": \"சூரியனை நெருங்குகிறோம்\",\n      \"solarDoge\": \"சூரிய Doge\",\n      \"approachingGalaxy\": \"இதோ, விண்மீன்களை நெருங்கி வந்துவிட்டோம்\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"வாழ்க்கையுடன் ஒரு கிரகத்தைக் கண்டுபிடித்துவிட்டோம்\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/te/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"మరింత లోడ్ చేయండి\",\n    \"loading\": \"లోడ్ అవుతోంది...\",\n    \"noUsersFound\": \"వినియోగదారులు లేరు\",\n    \"ok\": \"సరే\",\n    \"yes\": \"అవును\",\n    \"no\": \"లేదు\",\n    \"cancel\": \"రద్దు చేయండి\",\n    \"save\": \"save\",\n    \"edit\": \"edit\",\n    \"delete\": \"delete\",\n    \"joinRoom\": \"join room\",\n    \"copyLink\": \"copy link\",\n    \"copied\": \"copied\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"మూలం కథ\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Report a Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"ప్రైవేట్ గదిలో లేని మరియు మీరు అనుసరించే వినియోగదారుల జాబితా.\",\n      \"currentRoom\": \"ప్రస్తుతం:\",\n      \"startPrivateRoom\": \"వారితో ఒక ప్రైవేట్ గదిని ప్రారంభించండి\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"గదిని సృష్టించండి\",\n      \"refresh\": \"Refresh\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"గది పోయింది, తిరిగి వెళ్ళు\",\n      \"shareRoomLink\": \"గదికి లింక్‌ను భాగస్వామ్యం చేయండి\",\n      \"inviteFollowers\": \"ఆన్‌లైన్‌లో ఉన్న మీ అనుచరులను మీరు ఆహ్వానించవచ్చు:\",\n      \"whenFollowersOnline\": \"మీ అనుచరులు ఆన్‌లైన్‌లో ఉన్నప్పుడు, వారు ఇక్కడ కనిపిస్తారు.\"\n    },\n    \"login\": {\n      \"headerText\": \"వాయిస్ సంభాషణలను చంద్రుడికి తీసుకువెళుతున్నాము 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"GitHub తో లాగిన్ అవ్వండి\",\n      \"loginTwitter\": \"Twitter తో లాగిన్ అవ్వండి\",\n      \"createTestUser\": \"create test user\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"logout\",\n      \"probablyLoading\": \"బహుశా లోడ్ అవుతోంది...\",\n      \"voiceSettings\": \"వాయిస్ సెట్టింగ్‌లకు వెళ్లండి\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"soundSettings\": \"సౌండ్  సెట్టింగ్‌లకు వెళ్లండి\",\n      \"deleteAccount\": \"delete account\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\n      \"goHomeMessage\": \"చింతించకండి. నువ్వు చేయగలవు\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"room\": {\n      \"speakers\": \"స్పీకర్లు\",\n      \"requestingToSpeak\": \"మాట్లాడటానికి అభ్యర్థిస్తోంది\",\n      \"listeners\": \"Listeners\",\n      \"allowAll\": \"Allow all\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"అన్వేషించు...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"సౌండ్స్\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"edit profile\",\n      \"followsYou\": \"Follows you\",\n      \"followers\": \"అనుచరులు\",\n      \"following\": \"మీ క్రింది వ్యక్తులు\",\n      \"followHim\": \"వ్యక్తిని అనుసరించండి\",\n      \"unfollow\": \"వ్యక్తిని అనుసరించవద్దు\",\n      \"followingHim\": \"following\",\n      \"copyProfileUrl\": \"copy profile url\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"వాయిస్ సెట్టింగ్‌లు\",\n      \"mic\": \"మైక్:\",\n      \"permissionError\": \"మైక్స్ కనుగొనబడలేదు, మీరు ఏదీ ప్లగ్ ఇన్ చేయలేదు లేదా ఈ వెబ్‌సైట్ అనుమతి ఇవ్వలేదు.\",\n      \"refresh\": \"మైక్ లిస్ట్  రిఫ్రెష్ చేయండి\",\n      \"volume\": \"volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"Enter app title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Banned Users\",\n      \"unban\": \"unban\",\n      \"noBans\": \"no one has been banned yet\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Leave current room\",\n      \"confirmLeaveRoom\": \"మీరు ఖచ్చితంగా నిష్క్రమించాలనుకుంటున్నారా?\",\n      \"leave\": \"Leave\",\n      \"inviteUsersToRoomBtn\": \"గదికి వినియోగదారులను ఆహ్వానించండి\",\n      \"invite\": \"ఆహ్వానించండి\",\n      \"toggleMuteMicBtn\": \"మ్యూట్ మైక్రోఫోన్‌ను టోగుల్ చేయండి\",\n      \"mute\": \"Mute\",\n      \"unmute\": \"Unmute\",\n      \"makeRoomPublicBtn\": \"గదిని పబ్లిక్‌గా చేయండి!\",\n      \"settings\": \"సెట్టింగులు\",\n      \"speaker\": \"స్పీకర్\",\n      \"listener\": \"వినేవారు\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"మీ పరికరానికి ప్రస్తుతం మద్దతు లేదు. మీరు \",\n      \"linkText\": \"ఫిర్యాదు సృష్టించవచ్చు\",\n      \"addSupport\": \"మరియు నేను మీ పరికరానికి మద్దతును జోడించడానికి ప్రయత్నిస్తాను.\"\n    },\n    \"inviteButton\": { \"invited\": \"invited\", \"inviteToRoom\": \"invite to room\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"మీ మైక్‌ను యాక్సెస్ చేయడానికి అనుమతి నిరాకరించబడింది (మీరు బ్రౌజర్ సెట్టింగ్‌ల్లోకి వెళ్లి పేజీని మళ్లీ లోడ్ చేయాల్సి ఉంటుంది)\",\n      \"dismiss\": \"దీన్ని కొట్టివేయాలా\",\n      \"tryAgain\": \"మళ్ళీ ప్రయత్నించండి\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"కీబైండ్ సెట్ చేయండి\",\n      \"listening\": \"నేను వింటున్నాను\",\n      \"toggleMuteKeybind\": \"మ్యూట్ కీబైండ్‌ను టోగుల్ చేయండి\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"టోగుల్  push-to-talk కీబైండ్\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"కొన్ని కారణాల వల్ల ఆడియో వినియోగదారుడు లేరు\"\n    },\n    \"addToCalendar\": { \"add\": \"క్యాలెండర్‌కు జోడించండి\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket was killed by the server. This usually happens when you open the website in another tab.\",\n      \"reconnect\": \"తిరిగి కనెక్ట్ చేయండి\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"పబ్లిక్ \",\n        \"private\": \"ప్రైవేట్ \",\n        \"roomName\": \"గది పేరు\",\n        \"roomDescription\": \"గది వివరణ\",\n        \"descriptionError\": \"గరిష్ట పొడవు 500\",\n        \"nameError\": \"2 నుండి 60 అక్షరాల పొడవు ఉండాలి\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"కొత్త గది సృష్టించబడింది\",\n        \"roomInviteFrom\": \"గది నుండి ఆహ్వానించండి\",\n        \"justStarted\": \"వారు ఇప్పుడే ప్రారంభించారు\",\n        \"likeToJoin\": \", మీరు చేరాలనుకుంటున్నారా?\",\n        \"inviteReceived\": \"మీరు ఆహ్వానించబడ్డారు\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"username taken\",\n        \"avatarUrlError\": \"Invalid image\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"length 2 to 50 characters\",\n        \"displayNameLabel\": \"Display Name\",\n        \"usernameError\": \"length 4 to 15 characters and only alphanumeric/underscore\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"max length of 160 characters\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"మీరు ఎప్పుడైనా సృష్టించిన ఏ గదిలోనైనా ఈ వినియోగదారుని నిరోధించాలనుకుంటున్నారా?\",\n        \"blockUser\": \"block user\",\n        \"makeMod\": \"make mod\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"స్పీకర్‌గా జోడించండి\",\n        \"moveToListener\": \"move to listener\",\n        \"banFromChat\": \"ban from chat\",\n        \"banFromRoom\": \"ban from room\",\n        \"goBackToListener\": \"go back to listener\",\n        \"deleteMessage\": \"ఈ సందేశాన్ని తొలగించండి\",\n        \"makeRoomCreator\": \"make room admin\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"నాకు మాట్లాడటానికి అనుమతి కావాలి\",\n        \"makePublic\": \"గదిని బహిరంగపరచండి\",\n        \"makePrivate\": \"గదిని ప్రైవేట్‌గా చేయండి\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"షెడ్యూల్డ్ రూములు\",\n      \"noneFound\": \"ఏదీ కనుగొనబడలేదు\",\n      \"allRooms\": \"అన్ని షెడ్యూల్ గదులు\",\n      \"myRooms\": \"నా షెడ్యూల్ గదులు\",\n      \"scheduleRoomHeader\": \"షెడ్యూల్ గది\",\n      \"startRoom\": \"కొత్త గది\",\n      \"modal\": {\n        \"needsFuture\": \"భవిష్యత్తులో ఉండాలి\",\n        \"roomName\": \"గది పేరు\",\n        \"roomDescription\": \"వివరణ\",\n        \"minLength\": \"కనిష్ట పొడవు 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"మీరు చాట్ నుండి నిషేధించబడ్డారు\",\n      \"waitAlert\": \"మరొక సందేశం పంపే ముందు మీరు ఒక్క క్షణం వేచి ఉండాలి\",\n      \"search\": \"వెతకండి\",\n      \"searchResults\": \"సెర్చ్ రిజల్ట్స్ \",\n      \"recent\": \"తరచుగా ఉపయోగించే అంశాలు\",\n      \"sendMessage\": \"సందేశం పంపండి\",\n      \"whisper\": \"Whisper\",\n      \"welcomeMessage\": \"చాట్‌కు స్వాగతం!\",\n      \"roomDescription\": \"గది వివరణ\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"టేకాఫ్\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/th/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"โหลดเพิ่มเติม\",\n    \"loading\": \"กำลังโหลด...\",\n    \"noUsersFound\": \"ไม่พบผู้ใช้\",\n    \"ok\": \"โอเค\",\n    \"yes\": \"ใช่\",\n    \"no\": \"ไม่\",\n    \"cancel\": \"ยกเลิก\",\n    \"save\": \"บันทึก\",\n    \"edit\": \"แก้ไข\",\n    \"delete\": \"ลบ\",\n    \"joinRoom\": \"เข้าห้อง\",\n    \"copyLink\": \"คัดลอกลิงก์\",\n    \"copied\": \"คัดลอกเรียบร้อย\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"โปรดระวัง การปิดการเข้าถึงต่าง ๆ ของ Dogehouse อาจทำให้ไม่สามารถใช้งานฟังก์ชั่นต่าง ๆ ได้\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"ปิดไมค์ | DogeHouse\",\n    \"deafenedTitle\": \"ปิดเสียง | DogeHouse\",\n    \"dashboard\": \"แดชบอร์ด\",\n    \"connectionTaken\": \"เชื่อมต่อแล้ว\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"จุดกำเนิด\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"แจ้งปัญหา\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"แบน\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"ห้องสาธารณะที่ผู้คนที่คุณกำลังติดตามใช้งานอยู่\",\n      \"currentRoom\": \"ขณะนี้อยู่ใน:\",\n      \"startPrivateRoom\": \"สร้างห้องส่วนตัว\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"ติดตาม\",\n      \"followingHim\": \"กำลังติดตาม\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"สร้างห้อง\",\n      \"refresh\": \"รีเฟรช\",\n      \"editRoom\": \"แก้ไขห้อง\",\n      \"desktopAlert\": \"ดาวน์โหลด Dogehouse เวอร์ชั่นเดสก์ท็อป ได้แล้ววันนี้!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"ไม่พบห้อง, กรุณากลับไปที่หน้าหลัก\",\n      \"shareRoomLink\": \"แชร์ลิงก์ของห้อง\",\n      \"inviteFollowers\": \"คุณสามารถเชิญผู้ติดตามของคุณที่กำลังใช้งานอยู่:\",\n      \"whenFollowersOnline\": \"เมื่อผู้ติดตามของคุณออนไลน์, พวกเขาจะปรากฎที่นี่\"\n    },\n    \"login\": {\n      \"headerText\": \"ทำให้แชทเสียงเป็นเรื่องง่าย 🚀\",\n      \"featureText_1\": \"ธีมสีดำ\",\n      \"featureText_2\": \"สมัครและใช้ได้ทุกคน\",\n      \"featureText_3\": \"สนับสนุนหลายแพลตฟอร์ม\",\n      \"featureText_4\": \"โอเพนซอร์ส\",\n      \"featureText_5\": \"แชทข้อความ\",\n      \"featureText_6\": \"ขับเคลื่อนโดย Doge\",\n      \"loginGithub\": \"เข้าสู่ระบบด้วย GitHub\",\n      \"loginTwitter\": \"เข้าสู่ระบบด้วย Twitter\",\n      \"createTestUser\": \"สร้างผู้ใช้ทดลอง\",\n      \"loginDiscord\": \"เข้าสู่ระบบด้วย Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"ออกจากระบบ\",\n      \"probablyLoading\": \"กำลังโหลด...\",\n      \"voiceSettings\": \"การตั้งค่าเสียง\",\n      \"soundSettings\": \"การตั้งค่าการแจ้งเตือนเสียง\",\n      \"deleteAccount\": \"ลบบัญชี\",\n      \"overlaySettings\": \"การตั้งค่าโอเวอร์เลย์\",\n      \"couldNotFindUser\": \"ขออภัย ไม่สามารถหาผู้ใช้คนนั้นได้\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"โอ๊ะ! ไม่พบหน้าที่คุณค้นหา.\",\n      \"goHomeMessage\": \"ไม่ต้องกังวลไป คุณสามารถกลับไปที่\",\n      \"goHomeLinkText\": \"หน้าหลัก\"\n    },\n    \"room\": {\n      \"speakers\": \"ผู้พูด\",\n      \"requestingToSpeak\": \"กำลังขอขึ้นเป็นผู้พูด\",\n      \"listeners\": \"ผู้ฟัง\",\n      \"allowAll\": \"อนุญาตทั้งหมด\",\n      \"allowAllConfirm\": \"แน่ใจหรือไม่? คุณกำลังอนุมัติให้ผู้ใช้ {{count}} คน ขึ้นมาเป็นผู้พูด\"\n    },\n    \"searchUser\": { \"search\": \"ค้นหา...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"การแจ้งเตือนเสียง\",\n      \"title\": \"การตั้งค่าเสียง\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"แก้ไขโปรไฟล์\",\n      \"followsYou\": \"กำลังติดตามคุณ\",\n      \"followers\": \"ผู้ติดตาม\",\n      \"following\": \"กำลังติดตาม\",\n      \"followHim\": \"ติดตาม\",\n      \"followingHim\": \"กำลังติดตาม\",\n      \"copyProfileUrl\": \"คัดลอกลิงก์โปรไฟล์\",\n      \"urlCopied\": \"คัดลอกลิงก์เรียบร้อย\",\n      \"unfollow\": \"เลิกติดตาม\",\n      \"about\": \"เกี่ยวกับ\",\n      \"bot\": \"บอท\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"การตั้งค่าเสียง\",\n      \"mic\": \"ไมโครโฟน:\",\n      \"permissionError\": \"ไม่พบไมโครโฟน, คุณอาจจะลืมเสียบไมโครโฟน หรือ ลืมที่จะให้สิทธิ์ไมโครโฟนแก่เว็บไซต์.\",\n      \"refresh\": \"รีเฟรชไมโครโฟน\",\n      \"volume\": \"ระดับเสียง:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"App Title ไม่ถูกต้อง\", \"label\": \"ใส่ App Title\" },\n      \"header\": \"การตั้งค่าโอเวอร์เลย์\"\n    },\n    \"download\": {\n      \"starting\": \"กำลังดาวน์โหลด...\",\n      \"failed\": \"ไม่สามารถดาวน์โหลดได้, โปรดลองใหม่ในภายหลัง.\",\n      \"visit_gh\": \"เยี่ยมชมใน Github\",\n      \"prompt\": \"คลิ๊กที่ปุ่มด้านล่างเพื่อเริ่มการดาวน์โหลด.\",\n      \"download_now\": \"ดาวน์โหลดตอนนี้\",\n      \"download_for\": \"ดาวน์โหลดสำหรับ %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"ผู้ใช้ที่ถูกแบน\",\n      \"unban\": \"ปลดแบน\",\n      \"noBans\": \"ขณะนี้ยังไม่มีคนที่ถูกแบน\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"ออกจากห้อง\",\n      \"confirmLeaveRoom\": \"คุณแน่ใจหรือไม่ที่จะออกจากห้อง?\",\n      \"leave\": \"ออก\",\n      \"inviteUsersToRoomBtn\": \"เชิญผู้ใช้เข้าห้อง\",\n      \"invite\": \"เชิญ\",\n      \"toggleMuteMicBtn\": \"เปิด/ปิด ไมโครโฟน\",\n      \"mute\": \"ปิดไมโครโฟน\",\n      \"unmute\": \"เปิดไมโครโฟน\",\n      \"makeRoomPublicBtn\": \"ทำให้ห้องเป็นสาธารณะ!\",\n      \"settings\": \"การตั้งค่า\",\n      \"speaker\": \"ผู้พูด\",\n      \"listener\": \"ผู้ฟัง\",\n      \"chat\": \"แชท\",\n      \"toggleDeafMicBtn\": \"เปิด/ปิด เสียง\",\n      \"deafen\": \"ปิดเสียง\",\n      \"undeafen\": \"เปิดเสียง\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"ขณะนี้เรายังไม่รองรับอุปกรณ์ของคุณ คุณสามารถแจ้งปัญหาได้ที่\",\n      \"linkText\": \" GitHub \",\n      \"addSupport\": \"และเรา จะพยายามแก้ไขให้รองรับอุปกรณ์ของคุณ.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"เชิญเรียบร้อย\",\n      \"inviteToRoom\": \"เชิญเข้าห้อง\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"การเข้าถึงไมโครโฟนถูกปฏิเสธ (คุณอาจจะต้องเปลี่ยนการตั้งค่าเบราว์เซอร์ และรีโหลดหน้านี้อีกครั้ง)\",\n      \"dismiss\": \"ปิด\",\n      \"tryAgain\": \"ลองใหม่อีกครั้ง\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"ตั้งค่าคีย์ลัด\",\n      \"listening\": \"กำลังตรวจจับ คีย์ลัด\",\n      \"toggleMuteKeybind\": \"คีย์ลัด เปิด/ปิด ไมโครโฟน\",\n      \"togglePushToTalkKeybind\": \"คีย์ลัด เปิด/ปิด Push to Talk\",\n      \"toggleOverlayKeybind\": \"คีย์ลัด เปิด/ปิด โอเวอร์เลย์\",\n      \"toggleDeafKeybind\": \"คีย์ลัด เปิด/ปิด เสียง\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"ไม่พบลำโพง\" },\n    \"addToCalendar\": { \"add\": \"เพิ่มไปยังปฏิทิน\" },\n    \"wsKilled\": {\n      \"description\": \"ตัดการเชื่อมต่อโดยเซิร์ฟเวอร์ อาจจะเป็นเพราะการเปิดเว็บไซต์หลายหน้าจอ\",\n      \"reconnect\": \"เชื่อมต่อใหม่อีกครั้ง\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"สาธารณะ\",\n        \"private\": \"ส่วนตัว\",\n        \"roomName\": \"ชื่อห้อง\",\n        \"roomDescription\": \"คำอธิบายห้อง\",\n        \"descriptionError\": \"สูงสุด 500 ตัวอักษร\",\n        \"nameError\": \"ต้องมีความยาวระหว่าง 2 ถึง 60 ตัวอักษร\",\n        \"subtitle\": \"กรอกข้อมูลในช่องต่อไปนี้เพื่อสร้างห้องใหม่\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"สร้างห้องเรียบร้อยแล้ว\",\n        \"roomInviteFrom\": \"คำเชิญจาก\",\n        \"justStarted\": \"พึ่งถูกสร้าง\",\n        \"likeToJoin\": \", คุณต้องการที่จะเข้าไปหรือไม่?\",\n        \"inviteReceived\": \"คุณถูกเชิญเข้าสู่\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"มีชื่อผู้ใช้นี้อยู่แล้ว\",\n        \"avatarUrlError\": \"รูปผิดพลาด\",\n        \"avatarUrlLabel\": \"URL โปรไฟล์จาก Github/Twitter/Discord\",\n        \"displayNameError\": \"ต้องมีความยาวระหว่าง 2 ถึง 50 ตัวอักษร\",\n        \"displayNameLabel\": \"ชื่อที่แสดง\",\n        \"usernameError\": \"ต้องมีความยาวระหว่าง 4 ถึง 15 ตัวอักษร และสามารถใส่ได้แค่ ตัวอักษรอังกฤษ, เลข และ Underscore เท่านั้น.\",\n        \"usernameLabel\": \"ชื่อผู้ใช้\",\n        \"bioError\": \"ความยาวสูงสุด 160 ตัวอักษร\",\n        \"bioLabel\": \"คำอธิบาย\",\n        \"bannerUrlLabel\": \"URL รูปหน้าปกทวิตเตอร์\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"คุณแน่ใจหรือไม่ที่จะบล็อกผู้ใช้นี้จากทุกห้องที่คุณสร้าง?\",\n        \"blockUser\": \"บล็อกผู้ใช้\",\n        \"makeMod\": \"ตั้งให้เป็นผู้ควบคุม\",\n        \"unmod\": \"ลบสิทธิ์ผู้ควบคุม\",\n        \"addAsSpeaker\": \"ตั้งให้เป็นผู้พูด\",\n        \"moveToListener\": \"ย้ายกลับไปเป็นผู้ฟัง\",\n        \"banFromChat\": \"แบนจากแชท\",\n        \"banFromRoom\": \"แบนจากห้อง\",\n        \"goBackToListener\": \"กลับไปเป็นผู้ฟัง\",\n        \"deleteMessage\": \"ลบข้อความ\",\n        \"makeRoomCreator\": \"แต่งตั้งเป็นแอดมินห้อง\",\n        \"unBanFromChat\": \"ปลดแบนจากแชท\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"ต้องใช้สิทธิ์ในการพูด\",\n        \"makePublic\": \"ตั้งให้เป็นห้องสาธารณะ\",\n        \"makePrivate\": \"ตั้งให้เป็นห้องส่วนตัว\",\n        \"renamePublic\": \"ตั้งชื่อห้องสาธารณะ\",\n        \"renamePrivate\": \"ตั้งชื่อห้องส่วนตัว\",\n        \"chatDisabled\": \"ปิดแชท\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"ผู้คน\",\n      \"online\": \"ออนไลน์\",\n      \"noOnline\": \"คุณมีเพื่อน 0 คนออนไลน์อยู่ขณะนี้\",\n      \"showMore\": \"ดูเพิ่มเติม\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"ห้องที่ใกล้จะมาถึง\",\n      \"exploreMoreRooms\": \"ค้นหาห้องเพิ่มเติม\"\n    },\n    \"search\": {\n      \"placeholder\": \"ค้นหาห้อง ผู้ใช้ หรือหมวดหมู่\",\n      \"placeholderShort\": \"ค้นหา\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"โปรไฟล์\",\n      \"language\": \"ภาษา\",\n      \"reportABug\": \"แจ้งปัญหา\",\n      \"useOldVersion\": \"ใช้เวอร์ชั่นเก่า\",\n      \"logOut\": {\n        \"button\": \"ออกจากระบบ\",\n        \"modalSubtitle\": \"คุณแน่ใจหรือไม่ที่จะออกจากระบบ\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"แก้ไขข้อบกพร่องเสียง\",\n        \"stopDebugger\": \"ปิดการแก้ไขข้อบกพร่อง\"\n      },\n      \"downloadApp\": \"ดาวน์โหลดแอป\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"กำหนดการห้อง\",\n      \"noneFound\": \"ไม่พบ\",\n      \"allRooms\": \"ห้องที่กำหนดการไว้\",\n      \"myRooms\": \"ห้องที่กำหนดการไว้ของฉัน\",\n      \"scheduleRoomHeader\": \"กำหนดการห้อง\",\n      \"startRoom\": \"สร้างห้อง\",\n      \"modal\": {\n        \"needsFuture\": \"จะต้องมีในอนาคต\",\n        \"roomName\": \"ชื่อห้อง\",\n        \"minLength\": \"ขั้นต่ำ 2 ตัวอักษร\",\n        \"roomDescription\": \"รายละเอียด\"\n      },\n      \"tommorow\": \"พรุ่งนี้\",\n      \"today\": \"วันนี้\",\n      \"deleteModal\": { \"areYouSure\": \"แน่ใจหรือไม่ที่จะลบกำหนดการนี้?\" }\n    },\n    \"roomChat\": {\n      \"title\": \"แชท\",\n      \"emotesSoon\": \"[อิโมทจะมาในเร็ว ๆ นี้]\",\n      \"bannedAlert\": \"คุณถูกแบนจากแชท\",\n      \"waitAlert\": \"คุณต้องรอก่อนที่จะส่งข้อความต่อไป\",\n      \"search\": \"ค้นหา\",\n      \"searchResults\": \"ผลการค้นหา\",\n      \"recent\": \"ที่ใช้ล่าสุด\",\n      \"sendMessage\": \"ส่งข้อความ\",\n      \"whisper\": \"กระซิบ\",\n      \"welcomeMessage\": \"ยินดีต้อนรับสู่ห้องแชท!\",\n      \"roomDescription\": \"คำอธิบายห้อง\",\n      \"disabled\": \"แชทถูกปิดสำหรับห้องนี้\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"กำลังเติมเชื้อเพลิงยาน\",\n      \"takingOff\": \"กำลังออกตัว\",\n      \"inSpace\": \"ถึงอวกาศแล้ว\",\n      \"approachingMoon\": \"กำลังมุ่งหน้าสู่ดวงจันทร์\",\n      \"lunarDoge\": \"ถึงดวงจันทร์แล้ว\",\n      \"approachingSun\": \"กำลังมุ่งหน้าสู่ดวงอาทิตย์\",\n      \"solarDoge\": \"ถึงดวงอาทิตย์แล้ว\",\n      \"approachingGalaxy\": \"กำลังงมุ่งหน้าสู่กาแลคซี่\",\n      \"galacticDoge\": \"ถึงกาแลคซี่แล้ว\",\n      \"spottedLife\": \"พบดาวเอเลี่ยนแล้ว!\"\n    },\n    \"feed\": { \"yourFeed\": \"ฟีดของคุณ\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/tl/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"mag-load pa\",\n    \"loading\": \"naglo-load...\",\n    \"noUsersFound\": \"Walang nahanap na user\",\n    \"ok\": \"ok\",\n    \"yes\": \"oo\",\n    \"no\": \"hindi\",\n    \"cancel\": \"Kanselahin\",\n    \"save\": \"i-save\",\n    \"edit\": \"i-edit\",\n    \"delete\": \"tanggalin\",\n    \"joinRoom\": \"sumali\",\n    \"copyLink\": \"kopyahin\",\n    \"copied\": \"kinopya\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Laging tandaan maaring magkaroon ng di inaasahang problema ang DogeHouse kapag walang accessibility permissions\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Pinagmulang Kwento\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Mag-ulat ng isang Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"Ban\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Listahan nang mga tao na hindi pribado at iyong sinundan\",\n      \"currentRoom\": \"kasalukuyan nasa:\",\n      \"startPrivateRoom\": \"magsimula ng isang pribadong silid sa kanila\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"I-follow\",\n      \"followingHim\": \"sinusubaybayan\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Bagong silid\",\n      \"editRoom\": \"Edit Room\",\n      \"refresh\": \"Refresh\",\n      \"desktopAlert\": \"I-Download ang DogeHouse desktop app ngayon!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"nawala ang room, bumalik ka\",\n      \"shareRoomLink\": \"i-share ang link sa room na ito\",\n      \"inviteFollowers\": \"Pwede kang mag imbita nang mga taga-subaybay na naka online\",\n      \"whenFollowersOnline\": \"Dito mo makikita ang iyong mga taga-subaybay na naka online\"\n    },\n    \"login\": {\n      \"headerText\": \"Dalhin ang mga paguusap sa boses sa buwan 🚀\",\n      \"featureText_1\": \"Itim na tema\",\n      \"featureText_2\": \"Buksan ang Pag-sign-Up\",\n      \"featureText_3\": \"Suporta sa Cross-Platform\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"Mag login gamit ang Github\",\n      \"loginTwitter\": \"Mag login gamit ang Twitter\",\n      \"loginDiscord\": \"Mag login gamit ang Discord\",\n      \"createTestUser\": \"Gumawa nang test user\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Mag-logout\",\n      \"probablyLoading\": \"naglo-load pa\",\n      \"voiceSettings\": \"pumunta sa voice settings\",\n      \"overlaySettings\": \"Overlay Settings\",\n      \"soundSettings\": \"pumunta sound settings\",\n      \"deleteAccount\": \"I-delete ang iyong account\",\n      \"couldNotFindUser\": \"Kinalulungkot namin na hindi matagpuan ang iyong hinahanap\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! Ang page na ito ay nawala.\",\n      \"goHomeMessage\": \"Wag mag-alala\",\n      \"goHomeLinkText\": \"bumalik sa home\"\n    },\n    \"room\": {\n      \"speakers\": \"Mga nagsasalita\",\n      \"requestingToSpeak\": \"Humihiling na magsalita\",\n      \"listeners\": \"Mga nakikinig\",\n      \"allowAll\": \"Payagan lahat\",\n      \"allowAllConfirm\": \"Sigurado? Ang buong bilang {{count}} na tao ay papayagang makapag salita\"\n    },\n    \"searchUser\": { \"search\": \"maghanap...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Tunog\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"I-edit ang profile\",\n      \"followsYou\": \"mga sumusunod sayo\",\n      \"followers\": \"mga tagasubaybay\",\n      \"following\": \"sinusubaybayan\",\n      \"followHim\": \"I-follow\",\n      \"unfollow\": \"Unfollow\",\n      \"followingHim\": \"mga sinusubaybayan\",\n      \"copyProfileUrl\": \"Copy Profile URL\",\n      \"urlCopied\": \"URL copied to clipboard\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Voice Settings\",\n      \"mic\": \"Mic:\",\n      \"permissionError\": \" Walang makitang mikropono, maaring ito ay hindi nakaplug o hindi nabigyang permiso ang website na ito\",\n      \"refresh\": \"I-refresh and listahan ng mikropono\",\n      \"volume\": \"Volume:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Overlay Settings\",\n      \"input\": {\n        \"errorMsg\": \"Please enter a valid app title\",\n        \"label\": \"App title\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Users na naka-ban\",\n      \"unban\": \"I-unban\",\n      \"noBans\": \"Wala pang naka ban\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Umalis sa room nato\",\n      \"confirmLeaveRoom\": \"Sigurado ka ba na aalis ka?\",\n      \"leave\": \"Umalis\",\n      \"inviteUsersToRoomBtn\": \"Mag imbita nang user sa room\",\n      \"invite\": \"imbitahan\",\n      \"toggleMuteMicBtn\": \"I-mute ang mikropono\",\n      \"mute\": \"I-mute\",\n      \"unmute\": \"I-unmute\",\n      \"makeRoomPublicBtn\": \"I-public ang room!\",\n      \"settings\": \"Mga Settings\",\n      \"speaker\": \"Nagsasalita\",\n      \"listener\": \"Nakikinig\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Pasensya na hindi pa supportado ang iyong device.\",\n      \"linkText\": \"isyu sa GitHub\",\n      \"addSupport\": \"at i-try kung lagyan nang supporta ang iyong device\"\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Naimbitahan\",\n      \"inviteToRoom\": \"imbitahan sa room\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Tinanggihan ng pahintulot na subukang i-access ang iyong mikropono (pwede ka pumunta sa browser settings at subukang i-reload ang page)\",\n      \"dismiss\": \"I-dismiss\",\n      \"tryAgain\": \"subukang muli\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Mag set ng keybind\",\n      \"listening\": \"nakikinig...\",\n      \"toggleMuteKeybind\": \"I-toggle ang mute keybind\",\n      \"toggleOverlayKeybind\": \"Toggle overlay keybind\",\n      \"togglePushToTalkKeybind\": \"I-toggle ang push-to-talk keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Walang makitang audio consumer\" },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore more rooms\"\n    },\n    \"addToCalendar\": { \"add\": \"Idagdag sa kalendaryo\" },\n    \"wsKilled\": {\n      \"description\": \"Pinatay ng server ang websocket. Karaniwang magyayari ito dahil naka bukas ang website sa ibang tab.\",\n      \"reconnect\": \"kumonekta\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"subtitle\": \"ulatan ang mga sumusunod para makapag simula ng bagong room\",\n        \"public\": \"Publiko\",\n        \"private\": \"Pribado\",\n        \"roomName\": \"Pangalan ng room\",\n        \"roomDescription\": \"Paglalarawan sa silid\",\n        \"descriptionError\": \"haba ng 500\",\n        \"nameError\": \"dapat ay nasa pagitan ng 2 hanggang 60 na character ang haba\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Bagong room ang nalikha\",\n        \"roomInviteFrom\": \"Inimbitahang room galing sa\",\n        \"justStarted\": \"Nagsimula lang sila sa\",\n        \"likeToJoin\": \", Gusto mo bang sumali?\",\n        \"inviteReceived\": \"naiimbitahan ka na\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"username ay nakuha na\",\n        \"avatarUrlError\": \"Di-wastong imahe\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar url\",\n        \"displayNameError\": \"haba ng 2 hanggang 50 na character\",\n        \"displayNameLabel\": \"Pangalan ng Display\",\n        \"usernameError\": \"haba ng 4 hanggang 15 na mga character at alphanumeric / underscor lamange\",\n        \"usernameLabel\": \"Username\",\n        \"bioError\": \"limitado hanggang 160 mga letra lamang\",\n        \"bioLabel\": \"Bio\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Sigurado kaba na i-block mo itong user sa pagsali sa ginawa mong room?\",\n        \"blockUser\": \"I-block ang user\",\n        \"makeMod\": \"gumawa ng tagapangulo\",\n        \"unmod\": \"alisin bilang tagapangulo\",\n        \"addAsSpeaker\": \"idagdag bilang tagapagsalita\",\n        \"moveToListener\": \"lumipat sa tagapakinig\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banFromChat\": \"ban sa chat\",\n        \"banFromRoom\": \"ban sa room\",\n        \"goBackToListener\": \"bumalik sa nakikinig\",\n        \"deleteMessage\": \"I-delete ang mensahe\",\n        \"makeRoomCreator\": \"gawing room admin\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"kailangan nag pahintulot para magsalita\",\n        \"makePublic\": \"gawing publiko ang room\",\n        \"makePrivate\": \"gawing pribado ang silid\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Your feed\" },\n    \"scheduledRooms\": {\n      \"title\": \"Mga Nakaiskedyul na\",\n      \"noneFound\": \"walang nakita\",\n      \"allRooms\": \"Lahat ng nakaiskedyul na room\",\n      \"myRooms\": \"ang aking nakaiskedyul na room\",\n      \"scheduleRoomHeader\": \"Iskedyul ng Room\",\n      \"startRoom\": \"I-start ang room\",\n      \"modal\": {\n        \"needsFuture\": \"kailangan sa hinaharap\",\n        \"roomName\": \"pangalan ng room\",\n        \"roomDescription\": \"Deskripsyon\",\n        \"minLength\": \"haba ng 2\"\n      },\n      \"tommorow\": \"Susunod na araw\",\n      \"today\": \"Ngayong araw\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Sigurado ka ba na gusto mong alisin ang naka-iskedyul na silid?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"Na ban ka sa chat\",\n      \"waitAlert\": \"Kailangan mong maghintay ng isang segundo bago magpadala ng isa pang mensahe\",\n      \"search\": \"Maghanap\",\n      \"searchResults\": \"Mga Resulta ng Paghahanap\",\n      \"recent\": \"Madalas ginagamit\",\n      \"sendMessage\": \"Magpadala ng Mensahe\",\n      \"whisper\": \"Pabulong\",\n      \"welcomeMessage\": \"Maligayang pagdating sa chat!\",\n      \"roomDescription\": \"deskripsyon\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Papaalis\",\n      \"inSpace\": \"sa kalawakan\",\n      \"approachingMoon\": \"Papalapit sa buwan\",\n      \"lunarDoge\": \"Lunar Doge\",\n      \"approachingSun\": \"Papalapit sa araw\",\n      \"solarDoge\": \"Solar Doge\",\n      \"approachingGalaxy\": \"Papalapit sa kalawakan\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet na may buhay na namataan\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/tp/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"tenpo kama la sin li lon\",\n    \"loading\": \"sin li kama...\",\n    \"noUsersFound\": \"jan li ala\",\n    \"ok\": \"pona a\",\n    \"yes\": \"pona\",\n    \"no\": \"ike\",\n    \"cancel\": \"pini\",\n    \"save\": \"sitelen\",\n    \"edit\": \"ante\",\n    \"delete\": \"weka\",\n    \"joinRoom\": \"tawa tawa tomo\",\n    \"copyLink\": \"tu e sitelen tawa\",\n    \"copied\": \"ni li tu\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"DogeHouse li jo ala e ken la DogeHouse ken pali ala\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"sina ken ala e toki | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"sitelen tenpo pini ni\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"toki e ike\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"ken ala\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"jan ni li lon ala e tomo pi jan wan. sina lukin e jan ni.\",\n      \"currentRoom\": \"sina lon:\",\n      \"startPrivateRoom\": \"pali e tenpo poka ona\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"o lukin\",\n      \"followingHim\": \"sina lukin e ona\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"pali e tomo\",\n      \"refresh\": \"tenpo tu la kama jo e ni\",\n      \"editRoom\": \"ante e tomo\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"tomo li weka, tawa monsi\",\n      \"shareRoomLink\": \"kulupu e sitelen tawa tomo\",\n      \"inviteFollowers\": \"sina ken toki e ni tawa jan lon linja pi lukin e sina:\",\n      \"whenFollowersOnline\": \"jan pi lukin e sina li lon linja la ona li lon.\"\n    },\n    \"login\": {\n      \"headerText\": \"Tomo toki li tawa mun 🚀\",\n      \"featureText_1\": \"nasin lukin pimeja\",\n      \"featureText_2\": \"jan ale li ken e pali e jan\",\n      \"featureText_3\": \"pali kepeken ilo nanpa pi nasin ale\",\n      \"featureText_4\": \"sitelen kulupu\",\n      \"featureText_5\": \"pana e sitelen\",\n      \"featureText_6\": \"pali kepeken Doge\",\n      \"loginGithub\": \"nanpa e jan kepeken GitHub\",\n      \"loginTwitter\": \"nanpa e jan kepeken Twitter\",\n      \"createTestUser\": \"pali e jan pi tenpo lili\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"o sona ala e sina\",\n      \"probablyLoading\": \"ken la ni li kama...\",\n      \"voiceSettings\": \"open e sitelen lawa pi kalama uta\",\n      \"overlaySettings\": \"open e sitelen lawa pi sitelen sewi sitelen sina\",\n      \"soundSettings\": \"open e sitelen lawa pi kalama\",\n      \"deleteAccount\": \"pakala e jan sina\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Whoops! This page got lost in conversation.\",\n      \"goHomeMessage\": \"Not to worry. You can\",\n      \"goHomeLinkText\": \"go home\"\n    },\n    \"room\": {\n      \"speakers\": \"jan pi ken e toki\",\n      \"requestingToSpeak\": \"mi wile e toki\",\n      \"listeners\": \"jan pi kute\",\n      \"allowAll\": \"Ken jan ale e toki\",\n      \"allowAllConfirm\": \"sina wile e ni? ni li ken jan ale {{count}} e toki.\"\n    },\n    \"searchUser\": { \"search\": \"o lukin...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Kalama\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"ante e jan sina\",\n      \"followsYou\": \"li lukin e sina\",\n      \"followers\": \"jan ale ni li lukin e jan ni\",\n      \"following\": \"jan ni li lukin e jan ale ni\",\n      \"followHim\": \"o lukin\",\n      \"unfollow\": \"o lukin ala\",\n      \"followingHim\": \"sina li lukin e jan ni\",\n      \"copyProfileUrl\": \"tu e sitelen tawa pi jan ni\",\n      \"urlCopied\": \"ni li tu\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"sitelen lawa pi kalama uta\",\n      \"mic\": \"ilo pi lipu e kalama:\",\n      \"permissionError\": \"ilo pi lipu e kalama li lon ala, sina jo ala e ni anu lipu len ni ken ala e lipu e kalama.\",\n      \"refresh\": \"tenpo tu la kama jo e sitelen pi ilo pi lipu e kalama\",\n      \"volume\": \"wawa pi kalama:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"sitelen lawa pi sitelen sewi sitelen sina\",\n      \"input\": {\n        \"errorMsg\": \"Please enter valid app title\",\n        \"label\": \"o sitelen e nimi pi \"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"jan pi ken ala\",\n      \"unban\": \"ken ala ala\",\n      \"noBans\": \"jan ala li ken ala\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"tawa tan tomo ni\",\n      \"confirmLeaveRoom\": \"sina wile e tawa tan ni?\",\n      \"leave\": \"tawa tan ni\",\n      \"inviteUsersToRoomBtn\": \"toki e tomo ni tawa jan\",\n      \"invite\": \"toki e \\\"kama lon\\\"\",\n      \"toggleMuteMicBtn\": \"ante e ken e toki\",\n      \"mute\": \"ken ala e toki\",\n      \"unmute\": \"ken ala ala e toki\",\n      \"makeRoomPublicBtn\": \"kulupu e tomo ni!\",\n      \"settings\": \"sitelen lawa\",\n      \"speaker\": \"jan toki\",\n      \"listener\": \"jan kute\",\n      \"chat\": \"sitelen kulupu\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Lipu len ni li pali ala kepeken ilo nanpa sina. pali e\",\n      \"linkText\": \"sitelen ike lon GitHub la\",\n      \"addSupport\": \"mi ken ante e ni,mi ante e ni la ni pali kepeken ilo nanpa sina.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"sina li toki tawa jan ni e \\\"kama lon\\\"\",\n      \"inviteToRoom\": \"o toki tawa jan e \\\"kama lon\\\"\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Lipu len ni li ken ala e kepeken e ilo pi lipu e kalama sina (open e sitelen lawa pi ilo linluwi sina.tenpo tu la kama jo e lipu len ni.)\",\n      \"dismiss\": \"mi pilin ala tawa ni.\",\n      \"tryAgain\": \"ken la tenpo tu la ni li pali.\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"ante e nena\",\n      \"listening\": \"kute e nena\",\n      \"toggleMuteKeybind\": \"nena pi ante e ken toki\",\n      \"toggleOverlayKeybind\": \"nena pi ante e sitelen sewi sitelen sina\",\n      \"togglePushToTalkKeybind\": \"nena pi ante e \\\"utala la toki\\\"\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"ali li kute e kalama\" },\n    \"addToCalendar\": { \"add\": \"sitelen e ni lon lipu tenpo suno\" },\n    \"wsKilled\": {\n      \"description\": \"ilo pi pana sona li moli e linja sona. tenpo tu la sina open e lipu len ni la ni li kama.\",\n      \"reconnect\": \"tenpo tu la o pali e linja sona.\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"tomo kulupu\",\n        \"private\": \"tomo pi kulupu ala\",\n        \"roomName\": \"nimi pi tomo\",\n        \"roomDescription\": \"sitelen pi tomo\",\n        \"descriptionError\": \"anpa sewi pi suli li ale ale ale ale ale\",\n        \"nameError\": \"anpa pi suli li tu. anpa sewi pi suli li mute mute mute.\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"ona li pali e tomo.\",\n        \"roomInviteFrom\": \"jan li toki e tomo tawa sina. jan li\",\n        \"justStarted\": \"ona li open e tomo.\",\n        \"likeToJoin\": \", sina wile ala wile e tawa tawa tomo?\",\n        \"inviteReceived\": \"jan li toki tawa ni e \"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"jan sin li jo e nimi nanpa ni\",\n        \"avatarUrlError\": \"sitelen sina li ike\",\n        \"avatarUrlLabel\": \"sitelen tawa pi sitelen sina pi lipu Github/Twitter/Discord\",\n        \"displayNameError\": \"anpa pi suli li tu. anpa sewi pi suli li mute mute luka luka.\",\n        \"displayNameLabel\": \"nimi\",\n        \"usernameError\": \"anpa pi suli li tu tu. anpa sewi pi suli li luka luka luka. wan pi nimi li sitelen Latin anu nanpa anu linja anpa\",\n        \"usernameLabel\": \"nimi nanpa\",\n        \"bioError\": \"anpa sewi pi suli li ale mute mute mute\",\n        \"bioLabel\": \"sitelen sina\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Sina wile ala wile e ken ala e jan ni pi tomo sina ale?\",\n        \"blockUser\": \"ken ala e jan ni pi lukin e sina\",\n        \"makeMod\": \"ken e jan ni\",\n        \"unmod\": \"ken ala e jan ni\",\n        \"addAsSpeaker\": \"ken e jan ni pi toki\",\n        \"moveToListener\": \"ken ala e jan ni pi toki\",\n        \"banFromChat\": \"ken ala e jan ni pi sitelen\",\n        \"banFromRoom\": \"ken ala e jan ni pi tomo\",\n        \"goBackToListener\": \"o tawa monsi pi jan kute\",\n        \"deleteMessage\": \"weka e sitelen ni\",\n        \"makeRoomCreator\": \"ken e jan ni pi ken ale\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"wile e ken pi toki\",\n        \"makePublic\": \"kulupu e tomo\",\n        \"makePrivate\": \"kulupu ala e tomo\",\n        \"renamePublic\": \"ante e nimi tomo kulupu\",\n        \"renamePrivate\": \"ante e nimi tomo pi kulupu ala\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"tomo pi tenpo kama\",\n      \"noneFound\": \"tomo ala\",\n      \"allRooms\": \"tomo ale pi tenpo kama\",\n      \"myRooms\": \"tomo mi pi tenpo kama\",\n      \"scheduleRoomHeader\": \"pali e tomo pi tenpo kama\",\n      \"startRoom\": \"open e tomo\",\n      \"modal\": {\n        \"needsFuture\": \"tomo li wile e tenpo kama\",\n        \"roomName\": \"nimi tomo\",\n        \"roomDescription\": \"sitelen tomo\",\n        \"minLength\": \"anpa pi suli li tu tu.\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Toki sitelen\",\n      \"emotesSoon\": \"[tenpo kama la sinpin lili li ni]\",\n      \"bannedAlert\": \"ona li ken ala e sina pi toki sitelen\",\n      \"waitAlert\": \"o awen lili.\",\n      \"search\": \"alasa\",\n      \"searchResults\": \"sina alasa. mi sona e ni:\",\n      \"recent\": \"tenpo mute la sina kepeken e sinpin lili ni\",\n      \"sendMessage\": \"pana e sitelen\",\n      \"whisper\": \"Toki pi kalama lili\",\n      \"welcomeMessage\": \"Kama pona lon toki sitelen!\",\n      \"roomDescription\": \"sitelen tomo\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"telo suli li tawa tawa palisa pi kon pi sewi mute\",\n      \"takingOff\": \"ni li tawa tawa kon pi sewi mute\",\n      \"inSpace\": \"lon kon pi sewi mute\",\n      \"approachingMoon\": \"poka mun\",\n      \"lunarDoge\": \"soweli Doge lon mun\",\n      \"approachingSun\": \"poka suno\",\n      \"solarDoge\": \"soweli Doge lon suno\",\n      \"approachingGalaxy\": \"poka e kulupu suno\",\n      \"galacticDoge\": \"soweli Doge lon kulupu suno\",\n      \"spottedLife\": \"ona li lukin e ma. ma ni li jo e moli ala.\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/tr/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"Daha fazla yükle\",\n    \"loading\": \"Yükleniyor...\",\n    \"noUsersFound\": \"Hiçbir kullanıcı bulunamadı\",\n    \"ok\": \"Tamam\",\n    \"yes\": \"Evet\",\n    \"no\": \"Hayır\",\n    \"cancel\": \"İptal\",\n    \"save\": \"Kaydet\",\n    \"edit\": \"Düzenle\",\n    \"delete\": \"Sil\",\n    \"joinRoom\": \"Odaya katıl\",\n    \"copyLink\": \"Bağlantıyı kopyala\",\n    \"copied\": \"Kopyalandı!\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Erişilebilirlik izinleri olmadan DogeHouse'u çalıştırmanın istenmeyen hatalara neden olabileceğini lütfen unutmayın\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Ana başlık arayüz ulusallaştırma yazıları\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Susturulmuş | DogeHouse\",\n    \"deafenedTitle\": \"Sağırlaştırılmış | DogeHouse\",\n    \"dashboard\": \"Panel\",\n    \"connectionTaken\": \"Bağlantı Sağlandı\"\n  },\n  \"footer\": {\n    \"_comment\": \"Ana altbilgi arayüz ulusallaştırma yazıları\",\n    \"link_1\": \"DogeHouse Hikayesi\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Hata Bildir\"\n  },\n  \"pages\": {\n    \"_comment\": \"İlgili sayfa arayüz ulusallaştırma yazıları\",\n    \"admin\": {\n      \"ban\": \"Yasakla\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Senin takip ettiğin ve özel odada olmayan kullanıcıların listesi.\",\n      \"currentRoom\": \"Şu anki oda:\",\n      \"startPrivateRoom\": \"Onlarla özel bir oda oluştur\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Takip Et\",\n      \"followingHim\": \"Takiptesin\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Oda oluştur\",\n      \"refresh\": \"Yenile\",\n      \"editRoom\": \"Oda Düzenle\",\n      \"desktopAlert\": \"Bugün DogeHouse masaüstü uygulamasını indirin!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Oda gitti, geri dön\",\n      \"shareRoomLink\": \"Oda bağlantısını paylaş\",\n      \"inviteFollowers\": \"Çevrimiçi olan takipçilerini davet edebilirsin:\",\n      \"whenFollowersOnline\": \"Takipçilerin çevrimiçi olduğunda burada gözükecek.\"\n    },\n    \"login\": {\n      \"headerText\": \"Sesli sohbet aya gönderiliyor 🚀\",\n      \"featureText_1\": \"Karanlık Tema\",\n      \"featureText_2\": \"Açık kayıtlar\",\n      \"featureText_3\": \"Çoklu platform desteği\",\n      \"featureText_4\": \"Açık kaynak kodu\",\n      \"featureText_5\": \"Yazılı sohbet\",\n      \"featureText_6\": \"Doge tarafından desteklenmektedir\",\n      \"loginGithub\": \"GitHub ile giriş yap\",\n      \"loginTwitter\": \"Twitter ile giriş yap\",\n      \"createTestUser\": \"Test kullanıcısı oluştur\",\n      \"loginDiscord\": \"Discord ile giriş yap\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Çıkış yap\",\n      \"probablyLoading\": \"Muhtemelen yükleniyor...\",\n      \"voiceSettings\": \"Konuşma ayarlarına git\",\n      \"soundSettings\": \"Ses ayarlarına git\",\n      \"deleteAccount\": \"Hesabı sil\",\n      \"overlaySettings\": \"Kaplama ayarlarına git\",\n      \"couldNotFindUser\": \"Üzgünüz, böyle bir kullanıcı bulamadık\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Uups! Bu sayfa sohbette kayboldu.\",\n      \"goHomeMessage\": \"Endişe etme. Yapabilirsin\",\n      \"goHomeLinkText\": \"Ana sayfaya git\"\n    },\n    \"room\": {\n      \"speakers\": \"Konuşmacılar\",\n      \"requestingToSpeak\": \"Konuşmak için izin istiyor\",\n      \"listeners\": \"Dinleyiciler\",\n      \"allowAll\": \"Herkese izin ver\",\n      \"allowAllConfirm\": \"Emin misiniz? Bu, talep eden {{count}} kullanıcının konuşmasına izin verecektir\"\n    },\n    \"searchUser\": { \"search\": \"Ara...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Sesler\",\n      \"title\": \"Ses Ayarları\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profili düzenle\",\n      \"followsYou\": \"Seni takip ediyor\",\n      \"followers\": \"Takipçi\",\n      \"following\": \"Takip\",\n      \"followHim\": \"Takip Et\",\n      \"followingHim\": \"Takiptesin\",\n      \"copyProfileUrl\": \"Profil bağlantısını kopyala\",\n      \"urlCopied\": \"Bağlantı kopyalandı\",\n      \"unfollow\": \"Takipten çık\",\n      \"about\": \"\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Hakkında\",\n        \"rooms\": \"Odalar\",\n        \"scheduled\": \"Planlanmış\",\n        \"recorded\": \"Kaydedildi\",\n        \"clips\": \"Klipler\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Engelle\",\n      \"unblock\": \"Engeli kaldır\",\n      \"sendDM\": \"Özel mesaj gönder\",\n      \"aboutSuffix\": \"Hakkında\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ses Ayarları\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Mikrofon bulunamadı. Ya takılı değil ya da bu web sitesine izin vermediniz.\",\n      \"refesh\": \"Mikrofon listesini yenile\",\n      \"volume\": \"Ses seviyesi:\",\n      \"refresh\": \"Mikrofon listesini yenile\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Geçersiz uygulama başlığı\",\n        \"label\": \"Uygulama Başlığı Girin\"\n      },\n      \"header\": \"Kaplama ayarları\"\n    },\n    \"download\": {\n      \"starting\": \"İndirme başlıyor...\",\n      \"failed\": \"Otomatik indirme başarısız oldu, sonra tekrar dene\",\n      \"visit_gh\": \"Github Yayınlamalarına git\",\n      \"prompt\": \"İndirmeyi başlatmak için aşağıdaki butona bas\",\n      \"download_now\": \"Şimdi İndir\",\n      \"download_for\": \"%platform% için indir (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Bileşen arayüz ulusallaştırma yazıları\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Yasaklanan kullanıcılar\",\n      \"unban\": \"Yasağı kaldır\",\n      \"noBans\": \"Henüz kimse yasaklanmadı\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Odadan ayrıl\",\n      \"confirmLeaveRoom\": \"Ayrılmak istediğine emin misin?\",\n      \"leave\": \"Ayrıl\",\n      \"inviteUsersToRoomBtn\": \"Kullanıcıları odaya davet et\",\n      \"invite\": \"Davet et\",\n      \"toggleMuteMicBtn\": \"Mikrofon sesini aç/kapa\",\n      \"mute\": \"Sesi kapa\",\n      \"unmute\": \"Sesi aç\",\n      \"makeRoomPublicBtn\": \"Odayı herkese açık yap!\",\n      \"settings\": \"Ayarlar\",\n      \"speaker\": \"Konuşmacı\",\n      \"listener\": \"Dinleyici\",\n      \"chat\": \"Sohbet\",\n      \"toggleDeafMicBtn\": \"Sağırlaştırmayı aç/kapa\",\n      \"deafen\": \"Sağırlığı aç\",\n      \"undeafen\": \"Sağırlığı kapa\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Mikrofonunuza erişmeye çalışırken izin reddedildi (tarayıcı ayarlarına gitmeniz ve sayfayı yeniden yüklemeniz gerekebilir)\",\n      \"dismiss\": \"Reddet\",\n      \"tryAgain\": \"Yeniden dene\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Herkese açık\",\n        \"private\": \"Gizli\",\n        \"roomName\": \"Oda ismi\",\n        \"roomDescription\": \"Oda açıklaması\",\n        \"descriptionError\": \"Maksimum uzunluk 500\",\n        \"nameError\": \"2 ila 60 karakter uzunluğunda olmalıdır\",\n        \"subtitle\": \"Yeni bir oda başlatmak için gerekli alanları doldurun\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Yeni oda oluşturuldu\",\n        \"roomInviteFrom\": \"Oda daveti, tarafından\",\n        \"justStarted\": \"Başladılar\",\n        \"likeToJoin\": \", Katılmak ister misin?\",\n        \"inviteReceived\": \"Davet edildin, şuraya\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"Kullanıcı adı alınmış\",\n        \"avatarUrlError\": \"Geçersiz resim\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord avatar linki\",\n        \"displayNameError\": \"uzunluk 2-50 karakter arası olmalı\",\n        \"displayNameLabel\": \"Görünülecek isim\",\n        \"usernameError\": \"Uzunluk 4-15 arası olmalı ve sadece alfanümerik/alt_çizgi karakterlerini içermeli\",\n        \"usernameLabel\": \"Kullanıcı adı\",\n        \"bioError\": \"Maksimum uzunluk 160 karakter\",\n        \"bioLabel\": \"Biyo\",\n        \"bannerUrlLabel\": \"Twitter kapak bağlantısı\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Oluşturduğun tüm odalar için bu kullanıcıyı yasaklamak istediğine emin misin?\",\n        \"blockUser\": \"Kullanıcıyı Blokla\",\n        \"makeMod\": \"Moderatör Yap\",\n        \"unmod\": \"Moderatörlükten Çıkar\",\n        \"addAsSpeaker\": \"Konuşmacı Olarak Ekle\",\n        \"moveToListener\": \"Dinleyiciye taşı\",\n        \"banFromChat\": \"Sohbetten Yasakla\",\n        \"banFromRoom\": \"Odadan Yasakla\",\n        \"goBackToListener\": \"Dinlemeye Geri Dön\",\n        \"deleteMessage\": \"Mesajı Sil\",\n        \"makeRoomCreator\": \"Oda Yetkilisi Yap\",\n        \"unBanFromChat\": \"Sohbetten yasaklanmasını kaldır\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"Konuşmak için izne ihtiyacın var\",\n        \"makePublic\": \"Odayı herkese açık yap\",\n        \"makePrivate\": \"Odayı gizli yap\",\n        \"renamePublic\": \"Herkese açık oda adı seç\",\n        \"renamePrivate\": \"Gizli oda adı seç\",\n        \"chatDisabled\": \"sohbeti devre dışı bırak\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Nedense ses alıcısı yok\" },\n    \"addToCalendar\": { \"add\": \"Takvime Ekle\" },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Tuş Ata\",\n      \"listening\": \"Dinleniyor\",\n      \"toggleMuteKeybind\": \"susturma aç/kapat tuş ataması\",\n      \"togglePushToTalkKeybind\": \"bas-konuş tuş ataması\",\n      \"toggleOverlayKeybind\": \"kaplama tuş ataması\",\n      \"toggleDeafKeybind\": \"sağırlık aç/kapat tuş ataması\"\n    },\n    \"wsKilled\": {\n      \"description\": \"WebSocket bağlantısı sunucu tarafından kapatıldı. Bu genellikle websiteyi başka bir sekmede açtığında yaşanır.\",\n      \"reconnect\": \"Yeniden Bağlan\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Cihazınız şu anda desteklenmemektedir.\",\n      \"linkText\": \"GitHub ile bir issue (sorun)\",\n      \"addSupport\": \"oluşturabilirsiniz ve ben de cihazınıza destek eklemeye çalışırım.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Davet Edildi\",\n      \"inviteToRoom\": \"Odaya Davet Et\"\n    },\n    \"followingOnline\": {\n      \"people\": \"Arkadaşlar\",\n      \"online\": \"AKTİF\",\n      \"noOnline\": \"Şu anda hiçbir arkadaşın aktif değil\",\n      \"showMore\": \"Daha fazlasını göster\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Yaklaşan Odalar\",\n      \"exploreMoreRooms\": \"Daha Çok Oda Keşfet\"\n    },\n    \"search\": {\n      \"placeholder\": \"Oda, kullanıcı veya kategori ara...\",\n      \"placeholderShort\": \"Ara\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Dil\",\n      \"reportABug\": \"Hata Bildir\",\n      \"useOldVersion\": \"Eski sürümü kullan\",\n      \"logOut\": {\n        \"button\": \"Çıkış yap\",\n        \"modalSubtitle\": \"Çıkış yapmak istediğine emin misin?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Ses Hatalarını Ayıkla\",\n        \"stopDebugger\": \"Hata Ayıklamayı Durdur\"\n      },\n      \"downloadApp\": \"Uygulamayı İndir\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"scheduledRooms\": {\n      \"title\": \"Planlanmış Odalar\",\n      \"noneFound\": \"Oda bulunamadı\",\n      \"allRooms\": \"Tüm Planlanmış Odalar\",\n      \"myRooms\": \"Benim Planladığım Odalar\",\n      \"scheduleRoomHeader\": \"Oda Planla\",\n      \"startRoom\": \"Odayı Başlat\",\n      \"modal\": {\n        \"needsFuture\": \"Tarih gelecek bir zamanda olmalı\",\n        \"roomName\": \"Oda ismi\",\n        \"minLength\": \"Minimum oda ismi uzunluğu 2\",\n        \"roomDescription\": \"Açıklama\"\n      },\n      \"tommorow\": \"YARIN\",\n      \"today\": \"BUGÜN\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Bu planlanmış odayı silmek istediğinize emin misiniz?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Sohbet\",\n      \"emotesSoon\": \"[İfadeler yakında gelecek]\",\n      \"bannedAlert\": \"Sohbetten yasaklandın\",\n      \"waitAlert\": \"Başka bir mesaj göndermek için biraz beklemelisin\",\n      \"search\": \"Ara\",\n      \"searchResults\": \"Arama Sonuçları\",\n      \"recent\": \"Sık Kullanılanlar\",\n      \"sendMessage\": \"Mesaj Gönder\",\n      \"whisper\": \"Fısılda\",\n      \"welcomeMessage\": \"Sohbete hoş geldin!\",\n      \"roomDescription\": \"Oda Açıklaması\",\n      \"disabled\": \"oda sohbeti devre dışı bırakıldı\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Yakıt dolduruluyor\",\n      \"takingOff\": \"Kalkış\",\n      \"inSpace\": \"Uzayda\",\n      \"approachingMoon\": \"Ay yaklaşıyor\",\n      \"lunarDoge\": \"Ay doge'u\",\n      \"approachingSun\": \"Güneş yaklaşıyor\",\n      \"solarDoge\": \"Güneş doge'u\",\n      \"approachingGalaxy\": \"Galaksi yaklaşıyor\",\n      \"galacticDoge\": \"Galaktik doge'u\",\n      \"spottedLife\": \"Yaşam tespit edilen gezegen\"\n    },\n    \"feed\": { \"yourFeed\": \"Akışın\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/uk/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"завантажити ще\",\n    \"loading\": \"завантажується\",\n    \"noUsersFound\": \"користувача не знайдено\",\n    \"ok\": \"ОК\",\n    \"yes\": \"так\",\n    \"no\": \"нi\",\n    \"cancel\": \"відмiнити\",\n    \"save\": \"зберегти\",\n    \"edit\": \"редагувати\",\n    \"delete\": \"видалити\",\n    \"joinRoom\": \"Приєднатися до кімнати\",\n    \"copyLink\": \"Копіювати посилання\",\n    \"copied\": \"Скопійовано\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Без звуку | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Інформаційна панель\",\n    \"connectionTaken\": \"З'єднання встановлено\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Історія створення\",\n    \"link_2\": \"Діскорд\",\n    \"link_3\": \"Повідомити про помилку\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"заблокувати\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Список користувачів, які не перебувають у приватній кімнаті і за якими ви стежите\",\n      \"currentRoom\": \"зараз у:\",\n      \"startPrivateRoom\": \"почніть з ними приватну кімнату\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"відстежувати\",\n      \"followingHim\": \"відстежуєте\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Створити кімнату\",\n      \"refresh\": \"Оновити\",\n      \"editRoom\": \"Редагувати кімнату\",\n      \"desktopAlert\": \"Завантажте додаток DogeHouse сьогодні!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"кімнати вже немає, повернутися\",\n      \"shareRoomLink\": \"поділитися посиланням на кімнату\",\n      \"inviteFollowers\": \"Ви можете запросити своїх підписників, які є онлайн\",\n      \"whenFollowersOnline\": \"Коли ваші підписники будуть в мережі, вони з’являться тут.\"\n    },\n    \"login\": {\n      \"headerText\": \"Піднімаємо голосові розмови до Місяця 🚀\",\n      \"featureText_1\": \"Темна тема\",\n      \"featureText_2\": \"Відкриті підписки\",\n      \"featureText_3\": \"Крос-платформна підтримка\",\n      \"featureText_4\": \"Відкритий код\",\n      \"featureText_5\": \"Текстовий чат\",\n      \"featureText_6\": \"Працює на Doge\",\n      \"loginGithub\": \"увійти з GitHub\",\n      \"loginTwitter\": \"увійти з Twitter\",\n      \"createTestUser\": \"створити тестового користувача\",\n      \"loginDiscord\": \"увійти з Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"вийти\",\n      \"probablyLoading\": \"ймовірно завантажується...\",\n      \"voiceSettings\": \"перейти до голосових налаштувань\",\n      \"soundSettings\": \"перейти до звукових налаштувань\",\n      \"deleteAccount\": \"видалити аккаунт\",\n      \"overlaySettings\": \"перейти до налаштувань накладення\",\n      \"couldNotFindUser\": \"Вибачте, ми не змогли знайти даного користувача\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Упс! Ця сторінка загубилася в розмові.\",\n      \"goHomeMessage\": \"Не хвилюйтеся. Ви зможете\",\n      \"goHomeLinkText\": \"повернутися додому\"\n    },\n    \"room\": {\n      \"speakers\": \"Доповідачі\",\n      \"requestingToSpeak\": \"Запитую дозвіл говорити\",\n      \"listeners\": \"Слухачі\",\n      \"allowAll\": \"Дозволити все\",\n      \"allowAllConfirm\": \"Ви впевнені? Це дозволить усім {{count}} запитуючим користувачам розмовляти\"\n    },\n    \"searchUser\": { \"search\": \"шукати...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Звуки\",\n      \"title\": \"Налаштування звуку\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"редагувати профіль\",\n      \"followsYou\": \"підписані на вас\",\n      \"followers\": \"підписники\",\n      \"following\": \"ви підписані\",\n      \"followHim\": \"підписатися\",\n      \"followingHim\": \"відстежується\",\n      \"copyProfileUrl\": \"скопіювати посилання на профіль\",\n      \"urlCopied\": \"Посилання скопійовано\",\n      \"unfollow\": \"Відписатися\",\n      \"about\": \"About\",\n      \"bot\": \"Бот\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Голосові налаштування\",\n      \"mic\": \"мікрофон:\",\n      \"permissionError\": \"мікрофонів не знайдено, ви або немаєте їх увімкнутими, або не дали дозволу цьому веб-сайту.\",\n      \"refresh\": \"оновити список мікрофонів\",\n      \"volume\": \"гучність:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"Недійсне ім'я додатку\",\n        \"label\": \"Введіть назву додатку\"\n      },\n      \"header\": \"Налаштування накладення\"\n    },\n    \"download\": {\n      \"starting\": \"Початок завантаження...\",\n      \"failed\": \"Не вдалося виконати автоматичне завантаження, спробуйте ще раз пізніше\",\n      \"visit_gh\": \"Відвідайте Github Releases\",\n      \"prompt\": \"Натисніть на кнопку нижче, щоб розпочати завантаження\",\n      \"download_now\": \"Завантажити зараз\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Заборонені користувачі\",\n      \"unban\": \"разблокувати\",\n      \"noBans\": \"ще ніхто не був заблокованний\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Залишити поточну кімнату\",\n      \"confirmLeaveRoom\": \"Ві впевнені, что хочете піти?\",\n      \"leave\": \"Залишити\",\n      \"inviteUsersToRoomBtn\": \"Запросити користувачів до кімнати\",\n      \"invite\": \"Запросити\",\n      \"toggleMuteMicBtn\": \"Перемикнути мікрофон\",\n      \"mute\": \"Вимкнути звук\",\n      \"unmute\": \"Увімкнути звук\",\n      \"makeRoomPublicBtn\": \"Зробити кімнату загальнодоступною!\",\n      \"settings\": \"Налаштування\",\n      \"speaker\": \"Доповідач\",\n      \"listener\": \"Слухач\",\n      \"chat\": \"Чат\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Ваш пристрій не підтримується. Ви можете створити\",\n      \"linkText\": \"проблему на GitHub\",\n      \"addSupport\": \"і я спробую додати підтримку для вашого пристрою.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"запрошений\",\n      \"inviteToRoom\": \"запросити до кімнати\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"У доступі до мікрофона відмовлено (можливо, вам доведеться зайти в налаштування браузера та перезавантажити сторінку)\",\n      \"dismiss\": \"відхилити\",\n      \"tryAgain\": \"спробувати ще раз\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"встановити прив'язку клавіши\",\n      \"listening\": \"слухає\",\n      \"toggleMuteKeybind\": \"Перемикнути прив'язку клавіши\",\n      \"togglePushToTalkKeybind\": \"Перемикнути режим рації\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"немає аудіозв'язку\" },\n    \"addToCalendar\": { \"add\": \"Додати до календаря\" },\n    \"wsKilled\": {\n      \"description\": \"Вебсокет був видалений сервером. Зазвичай це відбувається, коли ви відкриваєте веб-сайт в іншій вкладці.\",\n      \"reconnect\": \"переєднатися\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"публічна\",\n        \"private\": \"приватна\",\n        \"roomName\": \"назва кімнати\",\n        \"roomDescription\": \"опис кімнати\",\n        \"descriptionError\": \"має містити до 500 символів\",\n        \"nameError\": \"має містити від 2 до 60 символів\",\n        \"subtitle\": \"Заповніть наступні поля для того, щоб розпочати нову кімнату\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Створено нову кімнату\",\n        \"roomInviteFrom\": \"Запрошення до кімнати від\",\n        \"justStarted\": \"Вони тільки почали\",\n        \"likeToJoin\": \", ви хотіли б приєднатися?\",\n        \"inviteReceived\": \"вас запросили\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"ім'я користувача зайнято\",\n        \"avatarUrlError\": \"Недійсне зображення\",\n        \"avatarUrlLabel\": \"URL-адреса аватару у GitHub / Twitter\",\n        \"displayNameError\": \"має містити від 2 до 60 символів\",\n        \"displayNameLabel\": \"Відображуване ім’я\",\n        \"usernameError\": \"має містити від 4 до 15 символів і бути лише буквено-цифровим та підкреслення\",\n        \"usernameLabel\": \"Ім'я користувача\",\n        \"bioError\": \"має містити до 160 символів\",\n        \"bioLabel\": \"Біографія\",\n        \"bannerUrlLabel\": \"URL-адреса банера Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Ви впевнені, що хочете заблокувати цього користувача в будь-якій кімнаті, яку ви коли-небудь створювали?\",\n        \"blockUser\": \"заблокувати користувача\",\n        \"makeMod\": \"зробити модератором\",\n        \"unmod\": \"повернути зі статусу модератора\",\n        \"addAsSpeaker\": \"додати як доповідача\",\n        \"moveToListener\": \"посунути до слухачів\",\n        \"banFromChat\": \"заблокувати в цьому чаті\",\n        \"banFromRoom\": \"заблокувати в цій кімнаті\",\n        \"goBackToListener\": \"повернутися у статус слухача\",\n        \"deleteMessage\": \"видалити це повідомлення\",\n        \"makeRoomCreator\": \"зробити адміном кімнати\",\n        \"unBanFromChat\": \"Розбанити у цьому чаті\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"запитувати дозвіл говорити\",\n        \"makePublic\": \"зробити кімнату загальнодоступною\",\n        \"makePrivate\": \"зробити кімнату приватною\",\n        \"renamePublic\": \"Встановити ім'я публічної кімнати\",\n        \"renamePrivate\": \"Встановити ім'я приватної кімнати\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Люди\",\n      \"online\": \"ОНЛАЙН\",\n      \"noOnline\": \"На даний момент у вас 0 друзів онлайн\",\n      \"showMore\": \"Показати більше\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Заплановані кімнати\",\n      \"exploreMoreRooms\": \"Знайти більше кімнат\"\n    },\n    \"search\": {\n      \"placeholder\": \"Знайти кімнати, користувачів або категорії\",\n      \"placeholderShort\": \"Пошук\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Профіль\",\n      \"language\": \"Мова\",\n      \"reportABug\": \"Повідомити про помилку\",\n      \"useOldVersion\": \"Використовувати стару версію\",\n      \"logOut\": {\n        \"button\": \"Вийти\",\n        \"modalSubtitle\": \"Ви впевнені що хочете вийти?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Налагодження звуку\",\n        \"stopDebugger\": \"Зупинити налагодження\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Заплановані Кімнати\",\n      \"noneFound\": \"не знайдено\",\n      \"allRooms\": \"усі заплановані кімнати\",\n      \"myRooms\": \"мої заплановані кімнати\",\n      \"scheduleRoomHeader\": \"Запланувати Кімнату\",\n      \"startRoom\": \"почати кімнату\",\n      \"modal\": {\n        \"needsFuture\": \"має бути в майбутньому\",\n        \"roomName\": \"назва кімнати\",\n        \"minLength\": \"має містити від 2 символів\",\n        \"roomDescription\": \"Опис\"\n      },\n      \"tommorow\": \"Завтра\",\n      \"today\": \"Cьогодні\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Ви дійсно бажаєте видалити цю заплановану кімнату?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Чат\",\n      \"emotesSoon\": \"[емоції незабаром]\",\n      \"bannedAlert\": \"Ви були заблокованні в цьому чаті\",\n      \"waitAlert\": \"Вам потрібно почекати, перш ніж надсилати інше повідомлення\",\n      \"search\": \"Шукати\",\n      \"searchResults\": \"Результати Пошуку\",\n      \"recent\": \"Часто використовуванні\",\n      \"sendMessage\": \"Надіслати Повідомлення\",\n      \"whisper\": \"Шепотіти\",\n      \"welcomeMessage\": \"Ласкаво просимо до чату!\",\n      \"roomDescription\": \"Опис кімнати\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Заправляємо ракету\",\n      \"takingOff\": \"Злітаємо\",\n      \"inSpace\": \"У космосі\",\n      \"approachingMoon\": \"Досягаємо луни\",\n      \"lunarDoge\": \"Місячний doge\",\n      \"approachingSun\": \"Досягаємо сонця\",\n      \"solarDoge\": \"Сонячний doge\",\n      \"approachingGalaxy\": \"Досягаємо галактики\",\n      \"galacticDoge\": \"Галактичний Doge\",\n      \"spottedLife\": \"Помічена планета з життям\"\n    },\n    \"feed\": { \"yourFeed\": \"Ваша Стрічка\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/ur/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"مزید لوڈ کریں\",\n    \"loading\": \"لوڈ ہو رہا ہے\",\n    \"noUsersFound\": \"کوئی صارف نہیں ملا\",\n    \"ok\": \"ٹھیک ہے\",\n    \"yes\": \"جی ہاں\",\n    \"no\": \"نہیں\",\n    \"cancel\": \"منسوخ کریں\",\n    \"save\": \"محفوظ کریں\",\n    \"edit\": \"ترمیم\",\n    \"delete\": \"حذف کریں\",\n    \"joinRoom\": \"روم میں شامل ہو جائیں \",\n    \"copyLink\": \"لنک کاپی کریں\",\n    \"copied\": \"کاپی کریں\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Please note running DogeHouse without accessibility permissions may cause unwanted errors\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Muted | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"اصل کہانی\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"بگ کی اطلاع دیں\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"پابندی لگائیں\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"وہ لوگ جنھیں آپ فالو کرتیں ہیں ور وو اس وقت نجی روم میں نہیں ہیں\",\n      \"currentRoom\": \"موجودہ روم:\",\n      \"startPrivateRoom\": \"ان کے ساتھ ایک نجی روم شروع کریں\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"follow\",\n      \"followingHim\": \"following\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"روم بنائیں\",\n      \"refresh\": \"ریفریش\",\n      \"editRoom\": \"Edit Room\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"یہ روم اب موجود نہیں ہے\",\n      \"shareRoomLink\": \"روم کی لنک شیئر کریں\",\n      \"inviteFollowers\": \"آپ اپنے فولّور کو دعوت دے سکتے ہیں جو آن لائن ہیں:\",\n      \"whenFollowersOnline\": \"جب آپ کے فولّور آن لائن ہوں گے تو وہ یہاں دکھائے جائیں گے۔\"\n    },\n    \"login\": {\n      \"headerText\": \"Taking voice conversations to the moon 🚀\",\n      \"featureText_1\": \"Dark Theme\",\n      \"featureText_2\": \"Open Sign-Ups\",\n      \"featureText_3\": \"Cross-Platform Support\",\n      \"featureText_4\": \"Open Source\",\n      \"featureText_5\": \"Text Chat\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"log in with GitHub\",\n      \"loginTwitter\": \"log in with Twitter\",\n      \"createTestUser\": \"create test user\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"لاگ آوٹ\",\n      \"probablyLoading\": \"شاید لوڈ ہو رہا ہے\",\n      \"voiceSettings\": \"اکاؤنٹ کی ترتیبات پر جائیں\",\n      \"soundSettings\": \"آواز کی ترتیبات پر جائیں\",\n      \"deleteAccount\": \"اکاؤنٹ ہمیشہ کے لئے ڈیلیٹ کریں\",\n      \"overlaySettings\": \"go to overlay settings\",\n      \"couldNotFindUser\": \"Sorry, we could not find that user\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"افوہ! لگتا ہے کچھ پنگا ہوگیا۔\",\n      \"goHomeMessage\": \"پریشانی کی بات نہیں. آپ کر سکتے ہیں\",\n      \"goHomeLinkText\": \"گھر جاو\"\n    },\n    \"room\": {\n      \"speakers\": \"اسپیکر\",\n      \"requestingToSpeak\": \"بولنے کی درخواسات\",\n      \"listeners\": \"سامعین\",\n      \"allowAll\": \"سب کو اجازت دیں\",\n      \"allowAllConfirm\": \"Are you sure? This will allow all {{count}} requesting users to speak\"\n    },\n    \"searchUser\": { \"search\": \"تلاش کریں\" },\n    \"soundEffectSettings\": {\n      \"header\": \"آواز کی ترتیبات\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"پروفائل میں ترمیم کریں\",\n      \"followsYou\": \"آپ کو فالو کیا ہوا ہے\",\n      \"followers\": \"فالور\",\n      \"following\": \"فالونگ\",\n      \"followHim\": \"فالو کریں\",\n      \"followingHim\": \"فالونگ\",\n      \"copyProfileUrl\": \"کاپی پروفائل یو آر ایل\",\n      \"urlCopied\": \"یو آر ایل کو کلپ بورڈ میں کاپی کیا گیا\",\n      \"unfollow\": \"ان فالو کریں\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"آواز کی ترتیبات\",\n      \"mic\": \"مائک:\",\n      \"permissionError\": \"کوئی mics نہیں ملا ، آپ نے یا تو کسی نے پلگ ان نہیں لیا ہے یا اس ویب سائٹ کو اجازت نہیں دی ہے۔\",\n      \"refresh\": \"مائک کی فہرست کو تازہ کریں\",\n      \"volume\": \"حجم:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"Invalid app title\", \"label\": \"Enter App Title\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"ممنوعہ صارفین\",\n      \"unban\": \"غیر پابندی\",\n      \"noBans\": \"ابھی تک کسی پر پابندی عائد نہیں کی گئی ہے\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"موجودہ کمرہ چھوڑ دو\",\n      \"confirmLeaveRoom\": \"کیا آپ واقعی رخصت ہونا چاہتے ہیں؟\",\n      \"leave\": \"چھوڑ دو\",\n      \"inviteUsersToRoomBtn\": \"صارفین کو کمرے میں مدعو کریں\",\n      \"invite\": \"مدعو کریں\",\n      \"toggleMuteMicBtn\": \"خاموش مائکروفون ٹوگل کریں\",\n      \"mute\": \"گونگا\",\n      \"unmute\": \"خاموش کریں\",\n      \"makeRoomPublicBtn\": \"کمرے کو عوامی بنائیں!\",\n      \"settings\": \"ترتیبات\",\n      \"speaker\": \"اسپیکر\",\n      \"listener\": \"سننے والا\",\n      \"chat\": \"چیٹ\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"آپ کا آلہ فی الحال تعاون یافتہ نہیں ہے۔ آپ ایک تشکیل دے سکتے ہیں\",\n      \"linkText\": \"issue on GitHub\",\n      \"addSupport\": \"اور میں آپ کے آلے کیلئے تعاون شامل کرنے کی کوشش کروں گا۔\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"مدعو کیا\",\n      \"inviteToRoom\": \"کمرے میں مدعو کریں\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"اجازت سے آپ کے مائک تک رسائی حاصل کرنے کی کوشش کرنے سے انکار ہوگیا (آپ کو براؤزر کی ترتیبات میں جانے اور پیج کو دوبارہ لوڈ کرنے کی ضرورت پڑسکتی ہے)\",\n      \"dismiss\": \"خارج کریں\",\n      \"tryAgain\": \"دوبارہ کوشش کریں\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"کی بائنڈ سیٹ کریں\",\n      \"listening\": \"سن رہا ہے\",\n      \"toggleMuteKeybind\": \"گونگا کی بائنڈ ٹوگل کریں\",\n      \"togglePushToTalkKeybind\": \"ٹوگل کریں پش ٹو ٹاک کی بائنڈ\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"کسی وجہ سے آڈیو صارف نہیں ہے\" },\n    \"addToCalendar\": { \"add\": \"کیلنڈر میں شامل کریں\" },\n    \"wsKilled\": {\n      \"description\": \"ویبسکیٹ سرور کے ذریعہ ہلاک ہوگئی تھی۔ یہ عام طور پر ہوتا ہے جب آپ کسی دوسرے ٹیب میں ویب سائٹ کھولتے ہیں۔\",\n      \"reconnect\": \"دوبارہ جڑنا\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"عوام\",\n        \"private\": \"نجی\",\n        \"roomName\": \"کمرے کا نام\",\n        \"roomDescription\": \"کمرے کی تفصیل\",\n        \"descriptionError\": \"زیادہ سے زیادہ لمبائی 500\",\n        \"nameError\": \"2 سے 60 حروف کے درمیان لمبا ہونا چاہئے\",\n        \"subtitle\": \"Fill the following fields to start a new room\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"نیا کمرہ تشکیل دیا گیا\",\n        \"roomInviteFrom\": \"کمرے سے دعوت نامہ\",\n        \"justStarted\": \"انہوں نے ابھی شروع کیا\",\n        \"likeToJoin\": \", کیا آپ اس میں شامل ہونا پسند کریں گے؟\",\n        \"inviteReceived\": \"آپ کو مدعو کیا گیا ہے\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"صارف نام پہلے ہی لیا گیا ہے\",\n        \"avatarUrlError\": \"غلط تصویر\",\n        \"avatarUrlLabel\": \"اوتار یو آر ایل Github / Twitter\",\n        \"displayNameError\": \"لمبائی 2 سے 50 حروف\",\n        \"displayNameLabel\": \"ڈسپلے نام\",\n        \"usernameError\": \"لمبائی 4 سے 15 حروف اور صرف حرفی / انگارے کی\",\n        \"usernameLabel\": \"صارف نام\",\n        \"bioError\": \"160 حروف کی زیادہ سے زیادہ لمبائی\",\n        \"bioLabel\": \"بایو\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"کیا آپ واقعی اس صارف کو اپنے بنائے ہوئے کسی بھی کمرے میں شامل ہونے سے روکنا چاہتے ہیں؟\",\n        \"blockUser\": \"بلوک یوزر\",\n        \"makeMod\": \"موڈ بنانا\",\n        \"unmod\": \"unmod\",\n        \"addAsSpeaker\": \"اسپیکر کے طور پر شامل کریں\",\n        \"moveToListener\": \"سننے والوں کی طرف بڑھیں\",\n        \"banFromChat\": \"چیٹ پر پابندی\",\n        \"banFromRoom\": \"کمرے سے پابندی لگانا\",\n        \"goBackToListener\": \"سننے والوں کے پاس واپس جائیں\",\n        \"deleteMessage\": \"اس پیغام کو حذف کریں\",\n        \"makeRoomCreator\": \"روم ایڈمن بنائیں\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"بولنے کی اجازت کی ضرورت ہے\",\n        \"makePublic\": \"کمرے کو عوامی بنائیں\",\n        \"makePrivate\": \"کمرے کو نجی بنائیں\",\n        \"renamePublic\": \"Set public room name\",\n        \"renamePrivate\": \"Set private room name\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"People\",\n      \"online\": \"ONLINE\",\n      \"noOnline\": \"You have 0 friends online right now\",\n      \"showMore\": \"Show more\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Upcoming rooms\",\n      \"exploreMoreRooms\": \"Explore More Rooms\"\n    },\n    \"search\": {\n      \"placeholder\": \"Search for rooms, users or categories\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profile\",\n      \"language\": \"Language\",\n      \"reportABug\": \"Report A Bug\",\n      \"useOldVersion\": \"Use Old Version\",\n      \"logOut\": {\n        \"button\": \"Log out\",\n        \"modalSubtitle\": \"Are you sure you want to logout?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"شیڈول کمرے\",\n      \"noneFound\": \"کچھ نہیں ملا\",\n      \"allRooms\": \"تمام طے شدہ کمرے\",\n      \"myRooms\": \"میرے طے شدہ کمرے\",\n      \"scheduleRoomHeader\": \"شیڈول روم\",\n      \"startRoom\": \"کمرہ شروع کریں\",\n      \"modal\": {\n        \"needsFuture\": \"مستقبل میں ہونے کی ضرورت ہے\",\n        \"roomName\": \"کمرے کا نام\",\n        \"minLength\": \"منٹ لمبائی 2\",\n        \"roomDescription\": \"کمرے کی تفصیل\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[emotes soon]\",\n      \"bannedAlert\": \"آپ کو چیٹ پر پابندی لگ گئی\",\n      \"waitAlert\": \"دوسرا پیغام بھیجنے سے پہلے آپ کو ایک سیکنڈ انتظار کرنا ہوگا\",\n      \"search\": \"تلاش کریں\",\n      \"searchResults\": \"تلاش کے نتائج\",\n      \"recent\": \"اکثر استعمال کیا جاتا ہے\",\n      \"sendMessage\": \"پیغام بھیجو\",\n      \"whisper\": \"سرگوشی\",\n      \"welcomeMessage\": \"چیٹ میں خوش آمدید!\",\n      \"roomDescription\": \"کمرے کی تفصیل\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Fueling rocket\",\n      \"takingOff\": \"Taking off\",\n      \"inSpace\": \"In space\",\n      \"approachingMoon\": \"Approaching moon\",\n      \"lunarDoge\": \"Lunar doge\",\n      \"approachingSun\": \"Approaching sun\",\n      \"solarDoge\": \"Solar doge\",\n      \"approachingGalaxy\": \"Approaching galaxy\",\n      \"galacticDoge\": \"Galactic Doge\",\n      \"spottedLife\": \"Planet with life spotted\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/uz/translation.json",
    "content": "{\n  \"_comment\": \"if you change this file, do: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"Ko'proq yuklash\",\n    \"loading\": \"Yuklanmoqda...\",\n    \"noUsersFound\": \"Foydalanuvchilar topilmadi\",\n    \"ok\": \"ОК\",\n    \"yes\": \"Ha\",\n    \"no\": \"Yo'q\",\n    \"cancel\": \"Bekor qilish\",\n    \"save\": \"Saqlash\",\n    \"edit\": \"Tahrirlash\",\n    \"delete\": \"O'chirish\",\n    \"joinRoom\": \"Xonaga qo'shilish\",\n    \"copyLink\": \"Havolani nusxalash\",\n    \"copied\": \"Nusxalandi\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"DogeHouse da kirish ruxsatisiz ishlash kutilmagan xatolarga olib kelishi mumkin\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Tovushsiz | DogeHouse\",\n    \"deafenedTitle\": \"Deafened | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Yaratilish tarixi\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Xato haqida xabar berish\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"taqiqlash\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Yopiq xonada bo'lmagan va siz obuna bo'lgan foydalanuvchilar ro'yxati.\",\n      \"currentRoom\": \"Hozirgi xona\",\n      \"startPrivateRoom\": \"Maxsus xona yarating\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"Obuna bo'lish\",\n      \"followingHim\": \"Obunachisiz \",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"Xona yaratish\",\n      \"refresh\": \"Yangilash\",\n      \"editRoom\": \"Xonani tahrirlash\",\n      \"desktopAlert\": \"Download the DogeHouse desktop app today!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"Xona yo'q, orqaga qayting\",\n      \"shareRoomLink\": \"Xona bilan bog'lanishni baham ko'ring\",\n      \"inviteFollowers\": \"Siz hozirda onlayn bo'lgan izdoshlaringizni taklif qilishingiz mumkin:\",\n      \"whenFollowersOnline\": \"Sizning izdoshlaringiz onlayn bo'lsa, ular bu yerda ko'rsatiladi.\"\n    },\n    \"login\": {\n      \"headerText\": \"Oyga ovozli aloqani ko'taramiz 🚀\",\n      \"featureText_1\": \"Qora qoplama\",\n      \"featureText_2\": \"Ochiq ro'yxatdan o'tish\",\n      \"featureText_3\": \"Krossplatformaviy\",\n      \"featureText_4\": \"Ochiq manba\",\n      \"featureText_5\": \"Matnli chat\",\n      \"featureText_6\": \"Doge tomonidan quvvatlanadi\",\n      \"loginGithub\": \"GitHub orqali kirish\",\n      \"loginTwitter\": \"Twitter orqali kirish\",\n      \"createTestUser\": \"Sinov foydalanuvchisini yarating\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"Chiqish\",\n      \"probablyLoading\": \"Menimcha yuklanmoqda...\",\n      \"voiceSettings\": \"Ovoz sozlamalari\",\n      \"soundSettings\": \"Tovush sozlamalari\",\n      \"deleteAccount\": \"Akkauntni o'chirish\",\n      \"overlaySettings\": \"Qoplama sozlamalari\",\n      \"couldNotFindUser\": \"Kechirasiz, biz bu foydalanuvchini topa olmadik\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Afsuski! Ushbu sahifa yo'qolgan.\",\n      \"goHomeMessage\": \"Xavotir olmang. Siz \",\n      \"goHomeLinkText\": \"uyga qayta olasiz\"\n    },\n    \"room\": {\n      \"speakers\": \"Hangomachilar\",\n      \"requestingToSpeak\": \"Gapirishni istaganlar\",\n      \"listeners\": \"tinglovchilar\",\n      \"allowAll\": \"Barchaga ruxsat berish\",\n      \"allowAllConfirm\": \"Ishonchingiz komilmi? Bu {{count}} foydalanuvchilariga gapirish uchun ruxsat so'rab murojaat qilganlarga gapirishga imkon beradi\"\n    },\n    \"searchUser\": { \"search\": \"Qidirish...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Tovushlar\",\n      \"title\": \"Sound Settings\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"Profilni tahrirlash\",\n      \"followsYou\": \"Obunachilaringiz\",\n      \"followers\": \"Obunachilar\",\n      \"following\": \"Obuna\",\n      \"followHim\": \"Obuna bo'lish\",\n      \"followingHim\": \"Obunachisiz\",\n      \"copyProfileUrl\": \"Profil URL manzilini nusxalash\",\n      \"urlCopied\": \"Profil URL nusxasi ko'chirildi\",\n      \"unfollow\": \"Obunani bekor qilish\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Ovoz Sozlamalari\",\n      \"mic\": \"Mikrofon:\",\n      \"permissionError\": \"Mikrofon topilmadi, ulanmagan yoki brauzerga tegishli ruxsat berilmagan.\",\n      \"refresh\": \"Mikrofonlar ro'yxatini yangilash\",\n      \"volume\": \"Tovush:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Qoplama Sozlamalari\",\n      \"input\": {\n        \"errorMsg\": \"Ilova nomi noto‘g‘ri\",\n        \"label\": \"Ilova nomini kiriting\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Starting download...\",\n      \"failed\": \"Could not auto-download, please try again later\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"Click on the button below to start download\",\n      \"download_now\": \"Download Now\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Bloklangan foydalanuvchilar\",\n      \"unban\": \"Unblock qilish\",\n      \"noBans\": \"Hali hech kim taqiqlanmagan\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Xonani tark etish\",\n      \"confirmLeaveRoom\": \"Haqiqatan ham tizimdan chiqishni xohlaysizmi?\",\n      \"leave\": \"Chiqish\",\n      \"inviteUsersToRoomBtn\": \"Foydalanuvchilarni xonaga taklif qilish\",\n      \"invite\": \"Taklif qilish\",\n      \"toggleMuteMicBtn\": \"Mikrofondi yoqish\",\n      \"mute\": \"Ovozni o'chirish\",\n      \"unmute\": \"Ovozni yoqish\",\n      \"makeRoomPublicBtn\": \"Xonani ochiq qiling!\",\n      \"settings\": \"sozlamalar\",\n      \"speaker\": \"Gapiruvchi\",\n      \"listener\": \"Tinglovchi\",\n      \"chat\": \"Chat\",\n      \"toggleDeafMicBtn\": \"Toggle Deafen\",\n      \"deafen\": \"Deafen\",\n      \"undeafen\": \"Undeafen\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Qurilmangiz qo'llab-quvvatlanmaydi. Siz \",\n      \"linkText\": \"GitHub orqali so'rov\",\n      \"addSupport\": \" yaratishingiz mumkin va biz sizning qurilmangizni qo'shishga harakat qilamiz.\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"Taklif qilindi!\",\n      \"inviteToRoom\": \"Xonaga taklif qilish\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Mikrofoningizga kirish imkonsiz (ehtimol brauzer sozlamalariga o'ting yoki sahifani qayta yuklang)\",\n      \"dismiss\": \"Rad etish\",\n      \"tryAgain\": \"Qayta urinib ko'rish\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"Keybind tayinlash\",\n      \"listening\": \"Tinglayabsiz...\",\n      \"toggleMuteKeybind\": \"Bosilganda mikrofonni faollashtirish\",\n      \"togglePushToTalkKeybind\": \"Raciya rejimini yoqing\",\n      \"toggleOverlayKeybind\": \"Keybind almashtirish tugmachasini biriktirish\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"Audio yo'q\" },\n    \"addToCalendar\": { \"add\": \"Taqvimga qo'shish\" },\n    \"wsKilled\": {\n      \"description\": \"Veb-server server tomonidan o'chirildi. Bu odatda saytni boshqa yorliqda ochganda yuz beradi.\",\n      \"reconnect\": \"Qayta qo'shilish\"\n    },\n    \"search\": {\n      \"placeholder\": \"Xonalarni, foydalanuvchilarni yoki toifalarni qidirish\",\n      \"placeholderShort\": \"Search\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Profil\",\n      \"language\": \"Til\",\n      \"reportABug\": \"Xato haqida xabar berish\",\n      \"useOldVersion\": \"Eski versiyadan foydalanish\",\n      \"logOut\": {\n        \"button\": \"Chiqish\",\n        \"modalSubtitle\": \"Chiqishga ishonchingiz komilmi?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Debug Audio\",\n        \"stopDebugger\": \"Stop Debugger\"\n      },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"umumiy\",\n        \"private\": \"xususiy\",\n        \"roomName\": \"xona nomi\",\n        \"roomDescription\": \"xona tavsifi\",\n        \"descriptionError\": \"maksimal uzunligi 500\",\n        \"nameError\": \"uzunligi 2 dan 60 ta belgidan iborat bo'lishi kerak\",\n        \"subtitle\": \"Yangi xonani boshlash uchun quyidagi maydonlarni to'ldiring\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Yangi xona yaratildi\",\n        \"roomInviteFrom\": \"Taklifnoma tomon\",\n        \"justStarted\": \"Ular endi boshladilar\",\n        \"likeToJoin\": \", qo'shilishni xohlaysizmi?\",\n        \"inviteReceived\": \"sizni taklif qilishdi\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"foydalanuvchi nomi olingan\",\n        \"avatarUrlError\": \"Noto'g'ri rasm\",\n        \"avatarUrlLabel\": \"GitHub / Twitter-dan avatar URL-manzili\",\n        \"displayNameError\": \"uzunligi 2 dan 50 gacha bo'lgan belgilar\",\n        \"displayNameLabel\": \"Ommaviy ism\",\n        \"usernameError\": \"Uzunligi 4 dan 15 gacha bo'lgan belgilar, alfasayısal va faqat pastki chiziq bilan belgilanadi\",\n        \"usernameLabel\": \"Foydalanuvchi nomi\",\n        \"bioError\": \"maksimal uzunligi 160 belgi\",\n        \"bioLabel\": \"Men haqimda\",\n        \"bannerUrlLabel\": \"Twitter banner URL\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Haqiqatan ham ushbu foydalanuvchi sizning xonalaringizga kirishiga to'sqinlik qilmoqchimisiz?\",\n        \"blockUser\": \"foydalanuvchini bloklash\",\n        \"makeMod\": \"moderator sifatida tayinlash\",\n        \"unmod\": \"moderatorlikdan mahrum qilish\",\n        \"addAsSpeaker\": \"Gapiruvchi sifatida qo'shish\",\n        \"moveToListener\": \"tinglovchilarga o'tkazish\",\n        \"banFromChat\": \"ushbu suhbatdan taqiqlash\",\n        \"banFromRoom\": \"ushbu xonadan taqiqlash\",\n        \"goBackToListener\": \"tinglovchiga aylanish\",\n        \"deleteMessage\": \"Xatni o'chirish\",\n        \"makeRoomCreator\": \"xonani admini qilish\",\n        \"unBanFromChat\": \"Unban from Chat\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"bu erda gapirish uchun ruxsat talab qilinadi\",\n        \"makePublic\": \"xonani umumiy qilish\",\n        \"makePrivate\": \"xonani xususiy qilish\",\n        \"renamePublic\": \"Umumiy xonani qayta nomlash\",\n        \"renamePrivate\": \"Xususiy xonani qayta nomlash\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Odamlar\",\n      \"online\": \"ONLAYN\",\n      \"noOnline\": \"Barcha do'stlaringiz offlayn\",\n      \"showMore\": \"Ko'proq ko'rsatish\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Yaqinlashib kelayotgan xonalar\",\n      \"exploreMoreRooms\": \"Boshqa xonalarni izlanish\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"feed\": { \"yourFeed\": \"Sizning tasmangiz\" },\n    \"scheduledRooms\": {\n      \"title\": \"Rejalashtirilgan xonalar\",\n      \"noneFound\": \"topilmadi\",\n      \"allRooms\": \"barcha rejalashtirilgan xonalar\",\n      \"myRooms\": \"mening rejalashtirilgan xonalarim\",\n      \"scheduleRoomHeader\": \"Rejalashtirilgan xonalar\",\n      \"startRoom\": \"xona ochish\",\n      \"modal\": {\n        \"needsFuture\": \"kelajakda paydo bo'lishi kerak\",\n        \"roomName\": \"xona nomi\",\n        \"roomDescription\": \"Tavsif\",\n        \"minLength\": \"minimal uzunlik 2\"\n      },\n      \"tommorow\": \"TOMMOROW\",\n      \"today\": \"TODAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Are you sure you want to delete this scheduled room?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Chat\",\n      \"emotesSoon\": \"[yaqinda emoji]\",\n      \"bannedAlert\": \"Siz ushbu suhbatdan bloklangansiz\",\n      \"waitAlert\": \"Keyingi xabarni yuborishdan oldin yana bir soniya kuting\",\n      \"search\": \"Qidirish\",\n      \"searchResults\": \"Qidiruv natijalari\",\n      \"recent\": \"Yaqindagilar\",\n      \"sendMessage\": \"Xat yuborish\",\n      \"whisper\": \"Pichirlash\",\n      \"welcomeMessage\": \"Suhbatga xush kelibsiz!\",\n      \"roomDescription\": \"xona tavsifi\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"raketaga yonilg'i quyilmoqda\",\n      \"takingOff\": \"dvigatellar ishga tushirldi\",\n      \"inSpace\": \"Fazoda\",\n      \"approachingMoon\": \"oyga yaqinlashmoqdamiz\",\n      \"lunarDoge\": \"Oyjon Doge\",\n      \"approachingSun\": \"quyoshga yaqinlashmoqdamiz\",\n      \"solarDoge\": \"Quyoshjon Doge\",\n      \"approachingGalaxy\": \"galaktikaga yaqinlashmoqdamiz\",\n      \"galacticDoge\": \"Galaktik Doge\",\n      \"spottedLife\": \"hayot mavjud bo'lgan sayyora aniqlandi\"\n    }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/vi/translation.json",
    "content": "{\n  \"_comment\": \"nếu bạn thay đổi tệp này, chạy lệnh: yarn i18\",\n  \"common\": {\n    \"loadMore\": \"tải thêm\",\n    \"loading\": \"đang tải...\",\n    \"noUsersFound\": \"không tìm thấy người dùng\",\n    \"ok\": \"ok\",\n    \"yes\": \"xác nhận\",\n    \"no\": \"không\",\n    \"cancel\": \"Hủy\",\n    \"save\": \"Lưu\",\n    \"edit\": \"Chỉnh sửa\",\n    \"delete\": \"Xóa\",\n    \"joinRoom\": \"Tham gia phòng\",\n    \"copyLink\": \"sao chép liên kết\",\n    \"copied\": \"đã sao chép liên kết\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"Lưu ý, không cung cấp đủ quyền truy cập cho DogeHouse có thể gây ra những lỗi không mong muốn\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"Tắt âm | DogeHouse\",\n    \"deafenedTitle\": \"Tắt tiếng | DogeHouse\",\n    \"dashboard\": \"Dashboard\",\n    \"connectionTaken\": \"Kết nối đã được thực hiện\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"Câu chuyện Khởi nguồn\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"Báo cáo sự cố\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"cấm\",\n      \"userStaffandContrib\": \"Nhân viên người dùng và đóng góp\",\n      \"staff\": \"Nhân viên: \",\n      \"contributions\": \"Người đóng góp\",\n      \"username\": \"Tên người dùng\",\n      \"usrStaff\": \"Nhân viên người dùng\",\n      \"usrContributions\": \"Đóng góp của người dùng\",\n      \"reason\": \"lý do\",\n      \"usernamePlaceholder\": \"tên người dùng đễ thực hiện các tác vụ\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"Danh sách người dùng bạn theo dõi đang ở trong một phòng nào đó mà không phải là phòng riêng.\",\n      \"currentRoom\": \"phòng hiện tại:\",\n      \"startPrivateRoom\": \"tạo phòng riêng tư với họ\",\n      \"title\": \"Mọi người\"\n    },\n    \"followList\": {\n      \"followHim\": \"theo dõi\",\n      \"followingHim\": \"đang theo dõi\",\n      \"title\": \"Mọi người\",\n      \"followingNone\": \"Không đang theo dõi ai\",\n      \"noFollowers\": \"Không có người theo dõi\"\n    },\n    \"home\": {\n      \"createRoom\": \"Tạo phòng\",\n      \"refresh\": \"Làm mới\",\n      \"editRoom\": \"Chỉnh sửa phòng\",\n      \"desktopAlert\": \"Tải xuống ứng dụng DogeHouse dành cho desktop ngay hôm nay!\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"phòng đã kết thúc, quay lại đi\",\n      \"shareRoomLink\": \"Chia sẻ liên kết phòng\",\n      \"inviteFollowers\": \"Bạn có thể mời những người theo dõi của bạn khi họ đang trực tuyến:\",\n      \"whenFollowersOnline\": \"Khi người theo dõi của bạn trực tuyến, họ sẽ hiện lên ở đây.\"\n    },\n    \"login\": {\n      \"headerText\": \"Đưa trải nghiệm trò chuyện trực tuyến lên mặt trăng🚀\",\n      \"featureText_1\": \"Chế độ tối\",\n      \"featureText_2\": \"Đăng ký Miễn phí\",\n      \"featureText_3\": \"Hỗ trợ Đa nền tảng\",\n      \"featureText_4\": \"Mã nguồn mở\",\n      \"featureText_5\": \"Trò chuyện bằng tin nhắn\",\n      \"featureText_6\": \"Được bảo kê bởi Doge\",\n      \"loginGithub\": \"đăng nhập với GitHub\",\n      \"loginTwitter\": \"đăng nhập với Twitter\",\n      \"createTestUser\": \"tạo người dùng thử\",\n      \"loginDiscord\": \"Login with Discord\"\n    },\n    \"myProfile\": {\n      \"logout\": \"đăng xuất\",\n      \"probablyLoading\": \"chắc là đang tải...\",\n      \"voiceSettings\": \"cài đặt giọng nói\",\n      \"overlaySettings\": \"cài đặt overlay\",\n      \"soundSettings\": \"cài đặt âm thanh\",\n      \"deleteAccount\": \"xóa tài khoản\",\n      \"couldNotFindUser\": \"Xin lỗi, chúng tôi không thể tìm thấy người dùng đó\",\n      \"privacySettings\": \"Cài đặt quyền riêng tư\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"Á! Đi lạc đâu đây.\",\n      \"goHomeMessage\": \"Đừng lo. Bạn có thể\",\n      \"goHomeLinkText\": \"quay về trang chủ\"\n    },\n    \"room\": {\n      \"speakers\": \"Diễn giả\",\n      \"requestingToSpeak\": \"Yêu cầu nói\",\n      \"listeners\": \"Thính giả\",\n      \"allowAll\": \"Cho phép tất cả\",\n      \"allowAllConfirm\": \"Bạn có chắc không? Hành động này sẽ cho phép {{count}} người dùng có quyền nói\"\n    },\n    \"searchUser\": { \"search\": \"tìm kiếm...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"Âm thanh\",\n      \"title\": \"Cài đặt âm thanh\",\n      \"playSound\": \"Phát âm thanh\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"chỉnh sửa hồ sơ\",\n      \"followsYou\": \"Theo dõi bạn\",\n      \"followers\": \"người theo dõi\",\n      \"following\": \"đang theo dõi\",\n      \"followHim\": \"Theo dõi\",\n      \"unfollow\": \"Bỏ theo dõi\",\n      \"followingHim\": \"đang theo dõi\",\n      \"copyProfileUrl\": \"sao chép liên kết hồ sơ\",\n      \"urlCopied\": \"Đã sao chép liên kết\",\n      \"about\": \"About\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"Về\",\n        \"rooms\": \"Phòng\",\n        \"scheduled\": \"Lên kế hoạch\",\n        \"recorded\": \"Ghi lại\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Quản trị viên\"\n      },\n      \"block\": \"Chặn\",\n      \"unblock\": \"Bỏ chặn\",\n      \"sendDM\": \"Gửi tin nhắn trực tiếp\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"Cài đặt giọng nói\",\n      \"mic\": \"mic:\",\n      \"permissionError\": \"không tìm thấy mic, bạn có thể chưa cắm hoặc chưa cấp quyền cho trang web này..\",\n      \"refresh\": \"làm mới danh sách mic\",\n      \"volume\": \"âm lượng:\",\n      \"title\": \"Cài đặt âm thanh\"\n    },\n    \"overlaySettings\": {\n      \"header\": \"Cài đặt Overlay\",\n      \"input\": {\n        \"errorMsg\": \"Vui lòng nhập tiêu đề hợp lý cho App\",\n        \"label\": \"Nhập tiêu đề cho app\"\n      }\n    },\n    \"download\": {\n      \"starting\": \"Đang bắt đầu tải xướng...\",\n      \"failed\": \"Không thể tự động tải xuống, vui lòng thử lại\",\n      \"visit_gh\": \"Ghé thăm bản Release trên Github\",\n      \"prompt\": \"Bấm vào nút dưới đây để bắt đầu tải\",\n      \"download_now\": \"Tải ngay\",\n      \"download_for\": \"Tải cho %nền tảng% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Cài đặt quyền riêng tư\",\n      \"header\": \"Cài đặt quyền riêng tư\",\n      \"whispers\": { \"label\": \"Thì thầm\", \"on\": \"Bật\", \"off\": \"Tắt\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"Nguời dùng bị cấm\",\n      \"unban\": \"bỏ cấm\",\n      \"noBans\": \"chưa có ai bị cấm\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"Thoát phòng hiện tại\",\n      \"confirmLeaveRoom\": \"Bạn có chắc muốn thoát không?\",\n      \"leave\": \"Thoát\",\n      \"inviteUsersToRoomBtn\": \"Mời người dùng vào phòng\",\n      \"invite\": \"Mời\",\n      \"toggleMuteMicBtn\": \"Tắt/mở mic\",\n      \"mute\": \"Tắt mic\",\n      \"unmute\": \"Mở mic\",\n      \"makeRoomPublicBtn\": \"Đặt phòng thành chế độ công khai!\",\n      \"settings\": \"Cài đặt\",\n      \"speaker\": \"Diễn giả\",\n      \"listener\": \"Thính giả\",\n      \"chat\": \"Trò chuyện\",\n      \"toggleDeafMicBtn\": \"Bật/tắt tắt tiếng\",\n      \"deafen\": \"Tắt tiếng\",\n      \"undeafen\": \"Bỏ tắt tiếng\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"Thiết bị của bạn hiện không được hỗ trợ. Bạn có thể tạo một\",\n      \"linkText\": \"lỗi tại GitHub\",\n      \"addSupport\": \"tôi sẽ thêm hỗ trợ cho thiết bị của bạn.\"\n    },\n    \"inviteButton\": { \"invited\": \"đã mời\", \"inviteToRoom\": \"mời vào phòng\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"Không có quyền truy cập mic của bạn (thử xem lại cài đặt của trình duyệt và tải lại trang)\",\n      \"dismiss\": \"bỏ qua\",\n      \"tryAgain\": \"thử lại\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"cài đặt tổ hợp phím\",\n      \"listening\": \"đang nghe\",\n      \"toggleMuteKeybind\": \"phím tắt/mở mic\",\n      \"toggleOverlayKeybind\": \"phím bật/tắt overlay\",\n      \"togglePushToTalkKeybind\": \"phím nhấn-để-nói\",\n      \"toggleDeafKeybind\": \"phím bật/tắt tắng tiếng\"\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"không có âm thanh vì một số lý do\"\n    },\n    \"addToCalendar\": { \"add\": \"Thêm vào lịch\" },\n    \"wsKilled\": {\n      \"description\": \"WebSocket đã bị ngắt kết nối bởi server. Điều này thường là do bạn đang mở trang web ở nhiều tab khác nhau.\",\n      \"reconnect\": \"kết nối lại\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"Công khai\",\n        \"private\": \"Riêng tư\",\n        \"roomName\": \"Tên phòng\",\n        \"roomDescription\": \"Mô tả phòng\",\n        \"descriptionError\": \"tối đa 500 ký tự\",\n        \"nameError\": \"phải từ 2 đến 60 ký tự\",\n        \"subtitle\": \"Điền vào các trường sau để bắt đầu một phòng mới\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"Đã tạo phòng mới\",\n        \"roomInviteFrom\": \"Mời vào phòng đến từ\",\n        \"justStarted\": \"Mọi người vừa chỉ mới bắt đầu\",\n        \"likeToJoin\": \", bạn có muốn vào không?\",\n        \"inviteReceived\": \"bạn đã được mời vào\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"tên người dùng đã tồn tại\",\n        \"avatarUrlError\": \"Hình ảnh không hợp lệ\",\n        \"avatarUrlLabel\": \"Liên kết đến ảnh đại diện Github/Twitter/Discord\",\n        \"displayNameError\": \"phải có độ dài từ 2 tới 50 ký tự\",\n        \"displayNameLabel\": \"Tên hiển thị\",\n        \"usernameError\": \"phải có độ dài từ 4 đến 15 ký tự và chỉ được dùng chữ, số hoặc gạch dưới\",\n        \"usernameLabel\": \"Tên người dùng\",\n        \"bioError\": \"tối đa 160 ký tự\",\n        \"bioLabel\": \"Mô tả\",\n        \"bannerUrlLabel\": \"URL ảnh bìa Twitter\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"Bạn có chắc chắn muốn cấm người dùng này tham gia bất kỳ phòng nào bạn tạo không?\",\n        \"blockUser\": \"cấm người dùng\",\n        \"makeMod\": \"cho làm kiểm duyệt viên\",\n        \"unmod\": \"bỏ làm kiểm duyệt viên\",\n        \"addAsSpeaker\": \"cho làm diễn giả\",\n        \"moveToListener\": \"cho làm thính giả\",\n        \"banFromChat\": \"cấm gửi tin nhắn\",\n        \"banFromRoom\": \"cấm khỏi phòng\",\n        \"goBackToListener\": \"trở về làm thính giả\",\n        \"deleteMessage\": \"xóa tin nhắn này\",\n        \"makeRoomCreator\": \"cho làm quản trị viên\",\n        \"unBanFromChat\": \"Huỷ cấm trò chuyện\",\n        \"banIPFromRoom\": \"Cấm địa chỉ IP khỏi phòng\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"phải được cấp quyền mới có thể nói\",\n        \"makePublic\": \"chuyển thành phòng công khai\",\n        \"makePrivate\": \"chuyển thành phòng riêng tư\",\n        \"renamePublic\": \"Đặt tên phòng công khai\",\n        \"renamePrivate\": \"Đặt tên phòng riêng\",\n        \"chatDisabled\": \"vô hiệu hoá chat\",\n        \"chatCooldown\": \"Thời gian hồi trò chuyện (giây)\",\n        \"chat\": {\n          \"label\": \"Trò chuyện\",\n          \"enabled\": \"Kích hoạt\",\n          \"disabled\": \"Vô hiệu hoá\",\n          \"followerOnly\": \"Chỉ người theo dõi\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"Mọi người\",\n      \"online\": \"Trực tuyến\",\n      \"noOnline\": \"Bạn có 0 người bạn trực tuyến ngay bây giờ\",\n      \"showMore\": \"Nhiều hơn\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"Các phòng sắp tới\",\n      \"exploreMoreRooms\": \"Khám phá các phòng khác\"\n    },\n    \"search\": {\n      \"placeholder\": \"Tìm theo phòng, người dùng, hoặc danh mục\",\n      \"placeholderShort\": \"Tìm kiếm\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"Hồ sơ\",\n      \"language\": \"Ngôn ngữ\",\n      \"reportABug\": \"Báo cáo lỗ hỏng\",\n      \"useOldVersion\": \"Sử dụng phiên bản cũ\",\n      \"logOut\": {\n        \"button\": \"Đăng xuất\",\n        \"modalSubtitle\": \"Bạn có chắc muốn đăng xuất?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"Gỡ lỗi âm thanh\",\n        \"stopDebugger\": \"Dừng trình gỡ lỗi\"\n      },\n      \"downloadApp\": \"Tải App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"Nhân viên của DogeHouse\",\n      \"dhContributor\": \"Người đóng góp cho DogeHouse\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Tin nhắn\",\n      \"showMore\": \"Nhiều hơn\",\n      \"noMessages\": \"Không có tin nhắn mới\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"Phòng đã lên lịch\",\n      \"noneFound\": \"không tìm thấy gì\",\n      \"allRooms\": \"tất cả phòng đã lên lịch\",\n      \"myRooms\": \"phòng do tôi lên lịch\",\n      \"scheduleRoomHeader\": \"Lên lịch phòng mới\",\n      \"startRoom\": \"bắt đầu phòng\",\n      \"modal\": {\n        \"needsFuture\": \"cần phải ở tương lai\",\n        \"roomName\": \"tên phòng\",\n        \"roomDescription\": \"Mô tả\",\n        \"minLength\": \"tối thiểu 2 ký tự\"\n      },\n      \"tommorow\": \"NGÀY MAI\",\n      \"today\": \"HÔM NAY\",\n      \"deleteModal\": {\n        \"areYouSure\": \"Bạn có chắc chắn muốn xóa phòng đã lên lịch này không?\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"Trò chuyện\",\n      \"emotesSoon\": \"[sắp có biểu tượng cảm xúc]\",\n      \"bannedAlert\": \"Bạn đã bị cấm gửi tin nhắn\",\n      \"waitAlert\": \"Bạn cần chờ một giây trước khi gửi tin nhắn kế tiếp\",\n      \"search\": \"Tìm kiếm\",\n      \"searchResults\": \"Kết quả tìm kiếm\",\n      \"recent\": \"Hay sử dụng\",\n      \"sendMessage\": \"Gửi tin nhắn\",\n      \"whisper\": \"Thì thầm\",\n      \"welcomeMessage\": \"Chào mừng đến với phòng trò chuyện!\",\n      \"roomDescription\": \"Mô tả phòng\",\n      \"disabled\": \"phòng chat đã bị vô hiệu hoá\",\n      \"messageDeletion\": {\n        \"message\": \"tin nhắn\",\n        \"retracted\": \"đã thu hồi\",\n        \"deleted\": \"đã xoá\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"Đang đổ xăng cho tên lửa\",\n      \"takingOff\": \"Cất cánh\",\n      \"inSpace\": \"Đang ở trên không gian\",\n      \"approachingMoon\": \"Đang tiếp cận mặt trăng\",\n      \"lunarDoge\": \"Doge đang ở trên mặt trăng\",\n      \"approachingSun\": \"Đang tiếp cận mặt trời\",\n      \"solarDoge\": \"Doge đang bay quanh hệ mặt trời\",\n      \"approachingGalaxy\": \"Đang tiếp cận dải ngân hà\",\n      \"galacticDoge\": \"Doge đang bay quanh thiên hà\",\n      \"spottedLife\": \"Đã tìm thấy hành tinh có sự sống\"\n    },\n    \"feed\": { \"yourFeed\": \"Bản tin của bạn\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/locales/zh-CN/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"加载更多\",\n    \"loading\": \"加载中...\",\n    \"noUsersFound\": \"没有找到任何用户\",\n    \"ok\": \"好\",\n    \"yes\": \"是\",\n    \"no\": \"否\",\n    \"cancel\": \"取消\",\n    \"save\": \"保存\",\n    \"edit\": \"编辑\",\n    \"delete\": \"删除\",\n    \"joinRoom\": \"加入音频聊天室\",\n    \"copyLink\": \"拷贝链接\",\n    \"copied\": \"已拷贝\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"请注意，在没有辅助功能权限的情况下运行DogeHouse可能会导致错误\",\n    \"copy\": \"复制\",\n    \"error\": \"错误\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"已关闭麦克风 | DogeHouse\",\n    \"deafenedTitle\": \"已静音 | DogeHouse\",\n    \"dashboard\": \"概览\",\n    \"connectionTaken\": \"连接已被占用\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"起源故事\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"报告Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"踢出用户\",\n      \"userStaffandContrib\": \"用户人员与贡献\",\n      \"staff\": \"人员: \",\n      \"contributions\": \"贡献\",\n      \"username\": \"用户名\",\n      \"usrStaff\": \"用户人员\",\n      \"usrContributions\": \"用户贡献\",\n      \"reason\": \"原因\",\n      \"usernamePlaceholder\": \"对该用户执行操作\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"您所关注的不在私人聊天室的人。\",\n      \"currentRoom\": \"所在的聊天室:\",\n      \"startPrivateRoom\": \"跟ta进入私人聊天室\",\n      \"title\": \"用户\"\n    },\n    \"followList\": {\n      \"followHim\": \"关注ta\",\n      \"followingHim\": \"已经关注\",\n      \"title\": \"用户\",\n      \"followingNone\": \"没有关注任何人\",\n      \"noFollowers\": \"没有粉丝\"\n    },\n    \"home\": {\n      \"createRoom\": \"新建聊天室\",\n      \"refresh\": \"刷新\",\n      \"editRoom\": \"修改聊天室\",\n      \"desktopAlert\": \"现在下载神烦狗桌面app吧！\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"聊天室已被删除，请回到前一页\",\n      \"shareRoomLink\": \"在聊天室分享链接\",\n      \"inviteFollowers\": \"您可以邀请您在线的关注者:\",\n      \"whenFollowersOnline\": \"当您的关注者在线的时候，ta们会在这里显示。\"\n    },\n    \"login\": {\n      \"headerText\": \"把语音聊天飞上月球 🚀\",\n      \"featureText_1\": \"深色主题\",\n      \"featureText_2\": \"开放注册\",\n      \"featureText_3\": \"跨平台支持\",\n      \"featureText_4\": \"开源\",\n      \"featureText_5\": \"文字聊天\",\n      \"featureText_6\": \"神烦狗出品\",\n      \"loginGithub\": \"用GitHub登录\",\n      \"loginTwitter\": \"用Twitter登录\",\n      \"createTestUser\": \"创建测试账号\",\n      \"loginDiscord\": \"用Discord登录\"\n    },\n    \"myProfile\": {\n      \"logout\": \"登出\",\n      \"probablyLoading\": \"加载中...\",\n      \"voiceSettings\": \"音量设置\",\n      \"soundSettings\": \"声音设置\",\n      \"deleteAccount\": \"删除账号\",\n      \"overlaySettings\": \"覆盖层设置\",\n      \"couldNotFindUser\": \"对不起，无法找到该用户\",\n      \"privacySettings\": \"隐私设置\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"哎呀，这页在聊天中被丢掉了\",\n      \"goHomeMessage\": \"别担心，可以\",\n      \"goHomeLinkText\": \"回到主页\"\n    },\n    \"room\": {\n      \"speakers\": \"讲话者\",\n      \"requestingToSpeak\": \"要求讲话\",\n      \"listeners\": \"听者\",\n      \"allowAll\": \"允许全体成员讲话\",\n      \"allowAllConfirm\": \"确定？这将会允许全部{{}}位成员讲话\"\n    },\n    \"searchUser\": {\n      \"search\": \"搜索...\"\n    },\n    \"soundEffectSettings\": {\n      \"header\": \"声音\",\n      \"title\": \"声音设置\",\n      \"playSound\": \"播放声音\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"编辑个人主页\",\n      \"followsYou\": \"关注您的人\",\n      \"followers\": \"关注者\",\n      \"following\": \"关注的人\",\n      \"followHim\": \"关注ta\",\n      \"followingHim\": \"已经关注\",\n      \"copyProfileUrl\": \"复制个人主页链接\",\n      \"urlCopied\": \"个人主页链接已复制到剪贴板\",\n      \"unfollow\": \"取消关注\",\n      \"about\": \"关于ta\",\n      \"bot\": \"机器人\",\n      \"profileTabs\": {\n        \"about\": \"关于\",\n        \"rooms\": \"聊天室\",\n        \"scheduled\": \"已安排\",\n        \"recorded\": \"已录制\",\n        \"clips\": \"片段\",\n        \"admin\": \"管理员\"\n      },\n      \"block\": \"屏蔽\",\n      \"unblock\": \"取消屏蔽\",\n      \"sendDM\": \"发送消息\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"此用户屏蔽了你\",\n        \"default\": \"噢！我们无法读取这个用户\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"语音设置\",\n      \"mic\": \"麦克风:\",\n      \"permissionError\": \"没有找到麦克风，也许没有插入麦克风，也许没有给此网页使用麦克风的权利。\",\n      \"refresh\": \"刷新麦克风列表\",\n      \"volume\": \"音量:\",\n      \"title\": \"声音设置\"\n    },\n    \"overlaySettings\": {\n      \"input\": {\n        \"errorMsg\": \"不允许的App标题\",\n        \"label\": \"输入App标题\"\n      },\n      \"header\": \"覆盖层设置\"\n    },\n    \"download\": {\n      \"starting\": \"开始下载...\",\n      \"failed\": \"无法自动下载，请稍后再尝试\",\n      \"visit_gh\": \"查看Github的发布\",\n      \"prompt\": \"按下面的按键开始下载\",\n      \"download_now\": \"下载\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"隐私设置\",\n      \"header\": \"隐私设置\",\n      \"whispers\": {\n        \"label\": \"私聊\",\n        \"on\": \"开\",\n        \"off\": \"关\"\n      }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"你的机器人\",\n      \"bots\": \"机器人\",\n      \"title\": \"机器人信息\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"重新生成\",\n      \"reveal\": \"点击显示ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"禁止加入的用户\",\n      \"unban\": \"解封\",\n      \"noBans\": \"没有人被封过\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"离开聊天室\",\n      \"confirmLeaveRoom\": \"确定离开吗?\",\n      \"leave\": \"离开\",\n      \"inviteUsersToRoomBtn\": \"邀请用户进入聊天室\",\n      \"invite\": \"邀请\",\n      \"toggleMuteMicBtn\": \"切换麦克风静音\",\n      \"mute\": \"关闭麦克风\",\n      \"unmute\": \"打开麦克风\",\n      \"makeRoomPublicBtn\": \"把聊天室设为公开!\",\n      \"settings\": \"设置\",\n      \"speaker\": \"扬声器\",\n      \"listener\": \"听者\",\n      \"chat\": \"文字聊天\",\n      \"toggleDeafMicBtn\": \"切换静音\",\n      \"deafen\": \"静音\",\n      \"undeafen\": \"取消静音\"\n    },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"麦克风使用权被拒绝（可能需要更改设置并且刷新页面）\",\n      \"dismiss\": \"取消\",\n      \"tryAgain\": \"重试\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"公开\",\n        \"private\": \"私人\",\n        \"roomName\": \"聊天室名字\",\n        \"roomDescription\": \"聊天室简介\",\n        \"descriptionError\": \"最大长度为500字符\",\n        \"nameError\": \"长度为2到60字符\",\n        \"subtitle\": \"填写以下信息开启新聊天室\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"新聊天室被建\",\n        \"roomInviteFrom\": \"邀请者:\",\n        \"justStarted\": \"ta们刚开始\",\n        \"likeToJoin\": \"，您要加入吗?\",\n        \"inviteReceived\": \"您被邀请到\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"用户名已被占用\",\n        \"avatarUrlError\": \"图片无效\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord头像URL\",\n        \"displayNameError\": \"长度为2到50字符\",\n        \"displayNameLabel\": \"所显名称\",\n        \"usernameError\": \"长度为4到15字符，只允许字母或数字或下划线\",\n        \"usernameLabel\": \"用户名\",\n        \"bioError\": \"最大长度为160字符\",\n        \"bioLabel\": \"个人简介\",\n        \"bannerUrlLabel\": \"Twitter横幅图链接\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"您确定把此人加入黑名单?（ta不可加入您所建立的任何聊天室）\",\n        \"blockUser\": \"加入黑名单\",\n        \"makeMod\": \"设为管理员\",\n        \"unmod\": \"取消管理员资格\",\n        \"addAsSpeaker\": \"加为讲话者\",\n        \"moveToListener\": \"移到听者\",\n        \"banFromChat\": \"禁止此人使用文字聊天\",\n        \"banFromRoom\": \"踢出聊天室\",\n        \"goBackToListener\": \"回到听者\",\n        \"deleteMessage\": \"删除此信息\",\n        \"makeRoomCreator\": \"设为聊天室管理员\",\n        \"unBanFromChat\": \"恢复此人使用文字聊天\",\n        \"banIPFromRoom\": \"本聊天室封禁此IP\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"需要允许才能说话\",\n        \"makePublic\": \"把聊天室设为公开\",\n        \"makePrivate\": \"把聊天室设为私人\",\n        \"renamePublic\": \"设置公开聊天室名称\",\n        \"renamePrivate\": \"设置私人聊天室名称\",\n        \"chatDisabled\": \"禁止聊天\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"聊天\",\n          \"enabled\": \"允许\",\n          \"disabled\": \"禁止\",\n          \"followerOnly\": \"仅粉丝\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"用户名被占用\",\n        \"subtitle\": \"请填写以下详情来创造机器人\",\n        \"title\": \"创造机器人\"\n      }\n    },\n    \"userVolumeSlider\": {\n      \"noAudioMessage\": \"no audio consumer for some reason\"\n    },\n    \"addToCalendar\": {\n      \"add\": \"加到日历\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"设置快捷键\",\n      \"listening\": \"等待输入\",\n      \"toggleMuteKeybind\": \"切换麦克风静音键绑定\",\n      \"togglePushToTalkKeybind\": \"切换按键说话绑定\",\n      \"toggleOverlayKeybind\": \"切换覆盖层绑定\",\n      \"toggleDeafKeybind\": \"切换静音键绑定\"\n    },\n    \"wsKilled\": {\n      \"description\": \"连接被服务器终止。当您在另一个标签中打开此网站时，通常会发生这种情况 \",\n      \"reconnect\": \"正在重新连接\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"您的设备展示还不被支持，您可以\",\n      \"linkText\": \"在GitHub开启一个报告\",\n      \"addSupport\": \"我将会尝试支持您的设备\"\n    },\n    \"inviteButton\": {\n      \"invited\": \"被邀请\",\n      \"inviteToRoom\": \"邀请进入聊天室\"\n    },\n    \"followingOnline\": {\n      \"people\": \"用户\",\n      \"online\": \"在线\",\n      \"noOnline\": \"你有0个朋友现在在线\",\n      \"showMore\": \"显示更多\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"即将开始的聊天室\",\n      \"exploreMoreRooms\": \"探寻更多聊天室\"\n    },\n    \"search\": {\n      \"placeholder\": \"搜索聊天室，用户或者种类\",\n      \"placeholderShort\": \"搜索\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"个人信息页面\",\n      \"language\": \"语言\",\n      \"reportABug\": \"反馈Bug\",\n      \"useOldVersion\": \"使用旧版本\",\n      \"logOut\": {\n        \"button\": \"登出\",\n        \"modalSubtitle\": \"是否确定登出?\"\n      },\n      \"debugAudio\": {\n        \"debugAudio\": \"调试声音\",\n        \"stopDebugger\": \"取消调试\"\n      },\n      \"downloadApp\": \"下载APP\",\n      \"developer\": \"开发者设置\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"神烦狗人员\",\n      \"dhContributor\": \"神烦狗贡献者\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"消息\",\n      \"showMore\": \"显示更多\",\n      \"noMessages\": \"没有新消息\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"预定的聊天室\",\n      \"noneFound\": \"没有找到\",\n      \"allRooms\": \"所有预定的聊天室\",\n      \"myRooms\": \"我的预定的聊天室\",\n      \"scheduleRoomHeader\": \"预定聊天室\",\n      \"startRoom\": \"开始聊天室\",\n      \"modal\": {\n        \"needsFuture\": \"需要一个在未来的时间\",\n        \"roomName\": \"聊天室名字\",\n        \"minLength\": \"最短长度为2\",\n        \"roomDescription\": \"聊天室描述\"\n      },\n      \"tommorow\": \"明天\",\n      \"today\": \"今天\",\n      \"deleteModal\": {\n        \"areYouSure\": \"你确定要删除此预定的聊天室？\"\n      }\n    },\n    \"roomChat\": {\n      \"title\": \"文字聊天\",\n      \"emotesSoon\": \"[未来增加表情]\",\n      \"bannedAlert\": \"您被禁止使用文字聊天\",\n      \"waitAlert\": \"您需要等一秒钟才能发出下一条信息\",\n      \"search\": \"搜索\",\n      \"searchResults\": \"搜索结果\",\n      \"recent\": \"经常使用的\",\n      \"sendMessage\": \"发一条信息\",\n      \"whisper\": \"私信\",\n      \"welcomeMessage\": \"欢迎来到文字聊天!\",\n      \"roomDescription\": \"聊天室介绍\",\n      \"disabled\": \"聊天室聊天已经被禁止\",\n      \"messageDeletion\": {\n        \"message\": \"消息\",\n        \"retracted\": \"已撤回\",\n        \"deleted\": \"已删除\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"火箭在加油\",\n      \"takingOff\": \"起飞\",\n      \"inSpace\": \"在太空\",\n      \"approachingMoon\": \"飞向月球\",\n      \"lunarDoge\": \"月球 Doge\",\n      \"approachingSun\": \"飞向太阳\",\n      \"solarDoge\": \"太阳 Doge\",\n      \"approachingGalaxy\": \"飞向宇宙\",\n      \"galacticDoge\": \"宇宙 Doge\",\n      \"spottedLife\": \"已发现新生命\"\n    },\n    \"feed\": {\n      \"yourFeed\": \"您的推送\"\n    }\n  }\n}"
  },
  {
    "path": "kibbeh/public/locales/zh-TW/translation.json",
    "content": "{\n  \"common\": {\n    \"loadMore\": \"載入更多\",\n    \"loading\": \"載入中...\",\n    \"noUsersFound\": \"沒有找到任何用戶\",\n    \"ok\": \"好\",\n    \"yes\": \"是\",\n    \"no\": \"否\",\n    \"cancel\": \"取消\",\n    \"save\": \"儲存\",\n    \"edit\": \"編輯\",\n    \"delete\": \"刪除\",\n    \"joinRoom\": \"加入房間\",\n    \"copyLink\": \"複製連結\",\n    \"copied\": \"已複製\",\n    \"formattedIntlDate\": \"{{date, intlDate}}\",\n    \"formattedIntlTime\": \"{{time, intlTime}}\",\n    \"requestPermissions\": \"請注意，在沒有存取權限下DogeHouse可能會出現運作異常\",\n    \"copy\": \"Copy\",\n    \"error\": \"Error\"\n  },\n  \"header\": {\n    \"_comment\": \"Main Header UI Internationalization Strings\",\n    \"title\": \"DogeHouse\",\n    \"mutedTitle\": \"已靜音 | DogeHouse\",\n    \"deafenedTitle\": \"已拒聽 | DogeHouse\",\n    \"dashboard\": \"儀錶版\",\n    \"connectionTaken\": \"Connection Taken\"\n  },\n  \"footer\": {\n    \"_comment\": \"Main Footer UI Internationalization Strings\",\n    \"link_1\": \"起源故事\",\n    \"link_2\": \"Discord\",\n    \"link_3\": \"回報Bug\"\n  },\n  \"pages\": {\n    \"_comment\": \"Respective Page UI Internationalization Strings\",\n    \"admin\": {\n      \"ban\": \"禁言\",\n      \"userStaffandContrib\": \"User Staff & Contributions\",\n      \"staff\": \"Staff: \",\n      \"contributions\": \"Contributions\",\n      \"username\": \"Username\",\n      \"usrStaff\": \"User Staff\",\n      \"usrContributions\": \"User Contributions\",\n      \"reason\": \"reason\",\n      \"usernamePlaceholder\": \"username to perform actions on\"\n    },\n    \"followingOnlineList\": {\n      \"listHeader\": \"你追蹤的且不在私人房間內的使用者\",\n      \"currentRoom\": \"目前的房間:\",\n      \"startPrivateRoom\": \"開啟私人房間\",\n      \"title\": \"People\"\n    },\n    \"followList\": {\n      \"followHim\": \"追蹤\",\n      \"followingHim\": \"已追蹤\",\n      \"title\": \"People\",\n      \"followingNone\": \"Not following anyone\",\n      \"noFollowers\": \"No followers\"\n    },\n    \"home\": {\n      \"createRoom\": \"新增房間\",\n      \"refresh\": \"重新整理\",\n      \"editRoom\": \"房間設定\",\n      \"desktopAlert\": \"下載DogeHouse桌上版吧！\"\n    },\n    \"inviteList\": {\n      \"roomGone\": \"房間已被删除，請回到前一頁\",\n      \"shareRoomLink\": \"在房間分享連結\",\n      \"inviteFollowers\": \"你可以邀請你在線上的追蹤者:\",\n      \"whenFollowersOnline\": \"當你的追蹤者在線上，他們會顯示在這裡\"\n    },\n    \"login\": {\n      \"headerText\": \"把語音對話帶上月球 🚀\",\n      \"featureText_1\": \"暗黑模式\",\n      \"featureText_2\": \"開放註冊\",\n      \"featureText_3\": \"跨平台支援\",\n      \"featureText_4\": \"開源\",\n      \"featureText_5\": \"文字聊天\",\n      \"featureText_6\": \"Powered by Doge\",\n      \"loginGithub\": \"以GitHub登入\",\n      \"loginTwitter\": \"以Twitter登入\",\n      \"createTestUser\": \"建立測試使用者\",\n      \"loginDiscord\": \"以Discord登入\"\n    },\n    \"myProfile\": {\n      \"logout\": \"登出\",\n      \"probablyLoading\": \"可能在讀取中...\",\n      \"voiceSettings\": \"音量設定\",\n      \"soundSettings\": \"聲音設定\",\n      \"deleteAccount\": \"刪除帳號\",\n      \"overlaySettings\": \"介面設定\",\n      \"couldNotFindUser\": \"很抱歉，我們找不到這名用戶\",\n      \"privacySettings\": \"Privacy settings\"\n    },\n    \"notFound\": {\n      \"whoopsError\": \"噢！這一頁不見了‧\",\n      \"goHomeMessage\": \"不用擔心，你可以\",\n      \"goHomeLinkText\": \"回到主頁面‧\"\n    },\n    \"room\": {\n      \"speakers\": \"講者\",\n      \"requestingToSpeak\": \"要求發言\",\n      \"listeners\": \"聽眾\",\n      \"allowAll\": \"允許所有\",\n      \"allowAllConfirm\": \"你確定嗎? 這將會允許所有 {{count}} 發出請求的用戶發言\"\n    },\n    \"searchUser\": { \"search\": \"搜尋...\" },\n    \"soundEffectSettings\": {\n      \"header\": \"聲音\",\n      \"title\": \"聲音設定\",\n      \"playSound\": \"Play Sound\"\n    },\n    \"viewUser\": {\n      \"editProfile\": \"編輯個人主頁\",\n      \"followsYou\": \"正在追蹤你\",\n      \"followers\": \"追蹤者們\",\n      \"following\": \"你正在追蹤\",\n      \"followHim\": \"追蹤\",\n      \"followingHim\": \"已追蹤\",\n      \"copyProfileUrl\": \"複製連結\",\n      \"urlCopied\": \"連結已被複製簿被\",\n      \"unfollow\": \"取消追蹤\",\n      \"about\": \"關於\",\n      \"bot\": \"Bot\",\n      \"profileTabs\": {\n        \"about\": \"About\",\n        \"rooms\": \"Rooms\",\n        \"scheduled\": \"Scheduled\",\n        \"recorded\": \"Recorded\",\n        \"clips\": \"Clips\",\n        \"admin\": \"Admin\"\n      },\n      \"block\": \"Block\",\n      \"unblock\": \"Unblock\",\n      \"sendDM\": \"Send DM\",\n      \"aboutSuffix\": \"\",\n      \"errors\": {\n        \"blocked\": \"This user has blocked you.\",\n        \"default\": \"Whoops! We couldn't load this user.\"\n      }\n    },\n    \"voiceSettings\": {\n      \"header\": \"語音設定\",\n      \"mic\": \"麥克風:\",\n      \"permissionError\": \"沒有找到麥克風，請確認有插入或者要允許本網站使用麥克風的權限‧\",\n      \"refresh\": \"更新麥克風列表\",\n      \"volume\": \"音量:\",\n      \"title\": \"Voice Settings\"\n    },\n    \"overlaySettings\": {\n      \"input\": { \"errorMsg\": \"無效的 app 標題 \", \"label\": \"輸入 App 標題\" },\n      \"header\": \"Overlay Settings\"\n    },\n    \"download\": {\n      \"starting\": \"正在下載 ...\",\n      \"failed\": \"自動下載失敗，請稍後再試\",\n      \"visit_gh\": \"Visit Github Releases\",\n      \"prompt\": \"點擊以下按鈕開始下載\",\n      \"download_now\": \"現在就下載\",\n      \"download_for\": \"Download for %platform% (%ext%)\"\n    },\n    \"privacySettings\": {\n      \"title\": \"Privacy Settings\",\n      \"header\": \"Privacy Settings\",\n      \"whispers\": { \"label\": \"Whispers\", \"on\": \"On\", \"off\": \"Off\" }\n    },\n    \"botEdit\": {\n      \"yourBots\": \"Your Bots\",\n      \"bots\": \"Bots\",\n      \"title\": \"Bot Information\",\n      \"apiKey\": \"ApiKey\",\n      \"regenerate\": \"Regenerate\",\n      \"reveal\": \"Click to reveal ApiKey\"\n    }\n  },\n  \"components\": {\n    \"_comment\": \"Component UI Internationalization Strings\",\n    \"avatar\": {},\n    \"backBar\": {},\n    \"blockedFromRoomUsers\": {\n      \"header\": \"被禁言的使用者\",\n      \"unban\": \"解禁\",\n      \"noBans\": \"沒有人被禁過\"\n    },\n    \"bottomVoiceControl\": {\n      \"leaveCurrentRoomBtn\": \"離開房間\",\n      \"confirmLeaveRoom\": \"確定離開嗎?\",\n      \"leave\": \"離開\",\n      \"inviteUsersToRoomBtn\": \"邀請使用者進入房間\",\n      \"invite\": \"邀請\",\n      \"toggleMuteMicBtn\": \"切換麥克風静音\",\n      \"mute\": \"麥克風靜音\",\n      \"unmute\": \"取消麥克風靜音\",\n      \"makeRoomPublicBtn\": \"把房間設定為公開\",\n      \"settings\": \"設定\",\n      \"speaker\": \"講者\",\n      \"listener\": \"聽眾\",\n      \"chat\": \"聊天框\",\n      \"toggleDeafMicBtn\": \"切換拒聽\",\n      \"deafen\": \"拒聽\",\n      \"undeafen\": \"取消拒聽\"\n    },\n    \"deviceNotSupported\": {\n      \"notSupported\": \"目前我們還未支援你的裝置，你可以到\",\n      \"linkText\": \" GitHub 建立 issue.\",\n      \"addSupport\": \" 而我會試著支援你的裝置‧\"\n    },\n    \"inviteButton\": { \"invited\": \"已被邀請\", \"inviteToRoom\": \"邀請到房間\" },\n    \"micPermissionBanner\": {\n      \"permissionDenied\": \"麥克風使用權被拒絕（可能需要更改設定並且重新整理頁面）\",\n      \"dismiss\": \"取消\",\n      \"tryAgain\": \"再試一次\"\n    },\n    \"keyboardShortcuts\": {\n      \"setKeybind\": \"設定按鍵組合\",\n      \"listening\": \"請輸入按鍵\",\n      \"toggleMuteKeybind\": \"觸發靜音的按鍵組合\",\n      \"togglePushToTalkKeybind\": \"觸發發言的按鍵組合\",\n      \"toggleOverlayKeybind\": \"toggle overlay keybind\",\n      \"toggleDeafKeybind\": \"Toggle deafen keybind\"\n    },\n    \"userVolumeSlider\": { \"noAudioMessage\": \"沒有聲音\" },\n    \"addToCalendar\": { \"add\": \"加到行事曆\" },\n    \"wsKilled\": {\n      \"description\": \"Websocket 已從 server 斷線，這通常發生在你於另一個分頁開啟本網站‧\",\n      \"reconnect\": \"重新連線\"\n    },\n    \"modals\": {\n      \"createRoomModal\": {\n        \"public\": \"公開\",\n        \"private\": \"私人\",\n        \"roomName\": \"房間名稱\",\n        \"roomDescription\": \"房間敘述\",\n        \"descriptionError\": \"最大長度為 500 字元\",\n        \"nameError\": \"名稱長度只能在 2 到 60 字元\",\n        \"subtitle\": \"填寫以下資料以開設房間\"\n      },\n      \"invitedToJoinRoomModal\": {\n        \"newRoomCreated\": \"新房間被建立\",\n        \"roomInviteFrom\": \"邀请者:\",\n        \"justStarted\": \"才剛開始\",\n        \"likeToJoin\": \"，你要加入嗎?\",\n        \"inviteReceived\": \"你被邀请到\"\n      },\n      \"editProfileModal\": {\n        \"usernameTaken\": \"用户名已被使用\",\n        \"avatarUrlError\": \"圖片無效\",\n        \"avatarUrlLabel\": \"Github/Twitter/Discord 頭像 URL\",\n        \"displayNameError\": \"長度為 2 到 50 字元\",\n        \"displayNameLabel\": \"顯示名稱\",\n        \"usernameError\": \"長度要在 4 到 15 字元之内，只可輸入英文字母、數字與下劃線 _\",\n        \"usernameLabel\": \"用户名\",\n        \"bioError\": \"最大長度為 160 字元\",\n        \"bioLabel\": \"個人簡介\",\n        \"bannerUrlLabel\": \"Twitter 橫額連結\"\n      },\n      \"profileModal\": {\n        \"blockUserConfirm\": \"你確定把此人加入黑名單?（他就無法加入你所建立的任何房間）\",\n        \"blockUser\": \"加入黑名單\",\n        \"makeMod\": \"設定為管理員\",\n        \"unmod\": \"取消管理員權限\",\n        \"addAsSpeaker\": \"加到講者\",\n        \"moveToListener\": \"移到聽眾\",\n        \"banFromChat\": \"禁止此人使用文字聊天\",\n        \"banFromRoom\": \"踢出房間\",\n        \"goBackToListener\": \"回到聽眾\",\n        \"deleteMessage\": \"删除此訊息\",\n        \"makeRoomCreator\": \"設定為房間主人\",\n        \"unBanFromChat\": \"解禁用戶使用文字聊天\",\n        \"banIPFromRoom\": \"Ban IP from Room\"\n      },\n      \"roomSettingsModal\": {\n        \"requirePermission\": \"需要許可才能發言\",\n        \"makePublic\": \"把房間設定為公開\",\n        \"makePrivate\": \"把房間設定為私人\",\n        \"renamePublic\": \"重設公開房間名字\",\n        \"renamePrivate\": \"重設私人房間名字\",\n        \"chatDisabled\": \"disable chat\",\n        \"chatCooldown\": \"Chat Cooldown (milliseconds)\",\n        \"chat\": {\n          \"label\": \"Chat\",\n          \"enabled\": \"Enabled\",\n          \"disabled\": \"Disabled\",\n          \"followerOnly\": \"Follower Only\"\n        }\n      },\n      \"createBotModal\": {\n        \"usernameTaken\": \"Username is taken\",\n        \"subtitle\": \"Please fill the details below to create your bot\",\n        \"title\": \"Create Bot\"\n      }\n    },\n    \"followingOnline\": {\n      \"people\": \"其他人\",\n      \"online\": \"上線\",\n      \"noOnline\": \"你有0個朋友\",\n      \"showMore\": \"顯示更多\"\n    },\n    \"upcomingRoomsCard\": {\n      \"upcomingRooms\": \"即將到來的房間\",\n      \"exploreMoreRooms\": \"搜尋更多房間\"\n    },\n    \"search\": {\n      \"placeholder\": \"搜尋房間，用戶及種類\",\n      \"placeholderShort\": \"搜尋\"\n    },\n    \"settingsDropdown\": {\n      \"profile\": \"個人檔案\",\n      \"language\": \"語言\",\n      \"reportABug\": \"回報漏洞\",\n      \"useOldVersion\": \"使用舊版本\",\n      \"logOut\": { \"button\": \"登出\", \"modalSubtitle\": \"你確定要登出嗎？\" },\n      \"debugAudio\": { \"debugAudio\": \"除錯音頻\", \"stopDebugger\": \"停止除錯\" },\n      \"downloadApp\": \"Download App\",\n      \"developer\": \"Developer Settings\"\n    },\n    \"userBadges\": {\n      \"dhStaff\": \"DogeHouse Staff\",\n      \"dhContributor\": \"DogeHouse Contributor\"\n    },\n    \"messagesDropdown\": {\n      \"title\": \"Messages\",\n      \"showMore\": \"Show More\",\n      \"noMessages\": \"No new messages\"\n    }\n  },\n  \"modules\": {\n    \"_comment\": \"Modules UI Internationalization Strings\",\n    \"scheduledRooms\": {\n      \"title\": \"預排的房間\",\n      \"noneFound\": \"沒有找到預排的房間\",\n      \"allRooms\": \"所有預排的房間\",\n      \"myRooms\": \"我預排的房間\",\n      \"scheduleRoomHeader\": \"預排房間\",\n      \"startRoom\": \"開始房間\",\n      \"modal\": {\n        \"needsFuture\": \"需要一個在未來的時間\",\n        \"roomName\": \"房間名稱\",\n        \"minLength\": \"最短長度為 2\",\n        \"roomDescription\": \"描述\"\n      },\n      \"tommorow\": \"明天\",\n      \"today\": \"今天\",\n      \"deleteModal\": { \"areYouSure\": \"你確定要刪除這間已排程的房間嗎？\" }\n    },\n    \"roomChat\": {\n      \"title\": \"房間\",\n      \"emotesSoon\": \"[將增加表情符號]\",\n      \"bannedAlert\": \"你被禁言了\",\n      \"waitAlert\": \"你需要等一秒鐘才能發出下一則訊息\",\n      \"search\": \"搜尋\",\n      \"searchResults\": \"搜尋结果\",\n      \"recent\": \"最近的\",\n      \"sendMessage\": \"發送訊息\",\n      \"whisper\": \"密語\",\n      \"welcomeMessage\": \"歡迎來到房間!\",\n      \"roomDescription\": \"房間簡介\",\n      \"disabled\": \"room chat has been disabled\",\n      \"messageDeletion\": {\n        \"message\": \"message\",\n        \"retracted\": \"retracted\",\n        \"deleted\": \"deleted\"\n      }\n    },\n    \"roomStatus\": {\n      \"fuelingRocket\": \"火箭正在加油\",\n      \"takingOff\": \"準備起飛\",\n      \"inSpace\": \"在太空\",\n      \"approachingMoon\": \"靠近月球中\",\n      \"lunarDoge\": \"月球上的狗狗\",\n      \"approachingSun\": \"靠近太陽中\",\n      \"solarDoge\": \"太陽系上的狗狗\",\n      \"approachingGalaxy\": \"靠近銀河系中\",\n      \"galacticDoge\": \"銀河上的狗狗\",\n      \"spottedLife\": \"發現星球上有生命跡象\"\n    },\n    \"feed\": { \"yourFeed\": \"Your Feed\" }\n  }\n}\n"
  },
  {
    "path": "kibbeh/public/manifest.json",
    "content": "{\n    \"name\": \"DogeHouse\",\n    \"short_name\": \"DogeHouse is taking voice conversations to the moon 🚀\",\n    \"start_url\": \"/dash\",\n    \"display\": \"minimal-ui\",\n    \"background_color\": \"#0b0e11\",\n    \"theme_color\": \"#fd4d4d\",\n    \"orientation\": \"portrait-primary\",\n    \"icons\": [\n      {\n        \"src\": \"/img/doge.png\",\n        \"type\": \"image/png\",\n        \"sizes\": \"200x200\"\n      },\n      {\n        \"src\": \"/img/doge512.png\",\n        \"type\": \"image/png\",\n        \"sizes\": \"512x512\"\n      }\n    ],\n    \"splash_pages\": null\n  }"
  },
  {
    "path": "kibbeh/public/privacy-policy.html",
    "content": "<style>\n    [data-custom-class='body'], [data-custom-class='body'] * {\n            background: transparent !important;\n          }\n  [data-custom-class='title'], [data-custom-class='title'] * {\n            font-family: Arial !important;\n  font-size: 26px !important;\n  color: #000000 !important;\n          }\n  [data-custom-class='subtitle'], [data-custom-class='subtitle'] * {\n            font-family: Arial !important;\n  color: #595959 !important;\n  font-size: 14px !important;\n          }\n  [data-custom-class='heading_1'], [data-custom-class='heading_1'] * {\n            font-family: Arial !important;\n  font-size: 19px !important;\n  color: #000000 !important;\n          }\n  [data-custom-class='heading_2'], [data-custom-class='heading_2'] * {\n            font-family: Arial !important;\n  font-size: 17px !important;\n  color: #000000 !important;\n          }\n  [data-custom-class='body_text'], [data-custom-class='body_text'] * {\n            color: #595959 !important;\n  font-size: 14px !important;\n  font-family: Arial !important;\n          }\n  [data-custom-class='link'], [data-custom-class='link'] * {\n            color: #3030F1 !important;\n  font-size: 14px !important;\n  font-family: Arial !important;\n  word-break: break-word !important;\n          }\n  </style>\n\n        <div data-custom-class=\"body\">\n        <div><strong><span style=\"font-size: 26px;\"><span data-custom-class=\"title\">PRIVACY NOTICE</span></span></strong></div><div><br></div><div><span style=\"color: rgb(127, 127, 127);\"><strong><span style=\"font-size: 15px;\"><span data-custom-class=\"subtitle\">Last updated <bdt class=\"question\">February 17, 2021</bdt></span></span></strong></span></div><div><br></div><div><br></div><div><br></div><div style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\">Thank you for choosing to be part of our community at <bdt class=\"question\">DogeHouse</bdt><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span> (\"<span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span><strong>Company</strong></span><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"><span data-custom-class=\"body_text\"></span></bdt></span></span></span></span></span><span data-custom-class=\"body_text\">\", \"<strong>we</strong>\", \"<strong>us</strong>\", \"<strong>our</strong>\"). We are committed to protecting your personal information and your right to privacy. If you have any questions or concerns about this privacy notice, or our practices with regards to your personal information, please contact us at <bdt class=\"question\">benawadapps@gmail.com</bdt>.</span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><br></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(127, 127, 127);\"><span data-custom-class=\"body_text\">When you <span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span>visit our website <bdt class=\"question\"><a href=\"https://dogehouse.tv\" target=\"_blank\" data-custom-class=\"link\">https://dogehouse.tv</a></bdt> (the \"<strong>Website</strong>\"), <span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span> and more generally, use any of our services (the \"<strong>Services</strong>\", which include the </span><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"><span data-custom-class=\"body_text\"></span></bdt>Website<span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span><bdt class=\"block-component\"></bdt></span></span></span><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></span></bdt></span></span></span></span><span data-custom-class=\"body_text\">), we appreciate that you are trusting us with your personal information. We take your privacy very seriously. In this privacy notice, we seek to explain to you in the clearest way possible what information we collect, how we use it and what rights you have in relation to it. We hope you take some time to read through it carefully, as it is important. If there are any terms in this privacy notice that you do not agree with, please discontinue use of our Services immediately.</span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><br></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">This privacy notice applies to all information collected through our Services (which, as described above, includes our </span><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"><span data-custom-class=\"body_text\"></span></bdt>Website<span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span><bdt class=\"block-component\"><bdt class=\"block-component\"></span></bdt></span></span></span></span><span data-custom-class=\"body_text\">), as well as, any related services, sales, marketing or events.</span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><br></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Please read this privacy notice carefully as it will help you understand what we do with the information that we collect.</strong></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><br></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">TABLE OF CONTENTS</span></strong></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><br></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#infocollect\"><span style=\"color: rgb(89, 89, 89);\">1. WHAT INFORMATION DO WE COLLECT?</span></a><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#infouse\"><span style=\"color: rgb(89, 89, 89);\">2. HOW DO WE USE YOUR INFORMATION?</span></a><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><a data-custom-class=\"link\" href=\"#infoshare\">3. WILL YOUR INFORMATION BE SHARED WITH ANYONE?</a></span><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span><bdt class=\"block-component\"></bdt><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#sociallogins\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\">4. HOW DO WE HANDLE YOUR SOCIAL LOGINS?</span></span></span></a><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#inforetain\"><span style=\"color: rgb(89, 89, 89);\">5. HOW LONG DO WE KEEP YOUR INFORMATION?</span></a><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#infosafe\"><span style=\"color: rgb(89, 89, 89);\">6. HOW DO WE KEEP YOUR INFORMATION SAFE?</span></a><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><a data-custom-class=\"link\" href=\"#privacyrights\">7. WHAT ARE YOUR PRIVACY RIGHTS?</a></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#DNT\"><span style=\"color: rgb(89, 89, 89);\">8. CONTROLS FOR DO-NOT-TRACK FEATURES</span></a></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#caresidents\"><span style=\"color: rgb(89, 89, 89);\">9. DO CALIFORNIA RESIDENTS HAVE SPECIFIC PRIVACY RIGHTS?</span></a></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"#policyupdates\"><span style=\"color: rgb(89, 89, 89);\">10. DO WE MAKE UPDATES TO THIS NOTICE?</span></a></span></div><div style=\"line-height: 1.5;\"><a data-custom-class=\"link\" href=\"#contact\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\">11. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?</span></a></div><div style=\"line-height: 1.5;\"><a data-custom-class=\"link\" href=\"#request\"><span style=\"color: rgb(89, 89, 89);\">12. HOW CAN YOU REVIEW, UPDATE OR DELETE THE DATA WE COLLECT FROM YOU?</span></a></div><div style=\"line-height: 1.5;\"><br></div><div id=\"infocollect\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">1. WHAT INFORMATION DO WE COLLECT?</span></strong><span data-custom-class=\"heading_1\"><bdt class=\"block-component\"><span data-custom-class=\"body_text\"></span></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span data-custom-class=\"heading_2\" style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><strong><u><br></u></strong><strong>Personal information you disclose to us</strong></span></span></div><div><div><br></div><div style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short: </em></strong></span></span></span></span><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em> </em></strong><em>We collect personal information that you provide to us.</em></span></span></span></span></span></span></div></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We collect personal information that you voluntarily provide to us when you <span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span>register on the </span><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website, <bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></span></bdt><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span><span data-custom-class=\"body_text\">express an interest in obtaining information about us or our products and Services, when you participate in activities on the <span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span><bdt class=\"block-component\"></bdt> (such as by posting messages in our online forums or entering competitions, contests or giveaways)<bdt class=\"statement-end-if-in-editor\"></bdt></span></span><span data-custom-class=\"body_text\"> or otherwise when you contact us.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">The personal information that we collect depends on the context of your interactions with us and the <span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span>, the choices you make and the products and features you use. The personal information we collect may include the following:<bdt class=\"block-component\"></bdt></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Personal Information Provided by You.</strong> We collect <span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"forloop-component\"></bdt><bdt class=\"question\">email addresses</bdt>; </span><span data-custom-class=\"body_text\"><bdt class=\"forloop-component\"></bdt><bdt class=\"question\">usernames</bdt>; </span><span data-custom-class=\"body_text\"><bdt class=\"forloop-component\"></bdt>and other similar information.</span><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"><bdt class=\"block-component\"></bdt></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Social Media Login Data. </strong>We may provide you with the option to register with us using your existing social media account details, like your Facebook, Twitter or other social media account. If you choose to register in this way, we will collect the information described in the section called \"<span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><a data-custom-class=\"link\" href=\"#sociallogins\">HOW DO WE HANDLE YOUR SOCIAL LOGINS</a></span></span></span></span>\" below.<span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"><bdt class=\"statement-end-if-in-editor\"></bdt></bdt></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">All personal information that you provide to us must be true, complete and accurate, and you must notify us of any changes to such personal information.<span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"><bdt class=\"statement-end-if-in-editor\"></bdt></bdt></span><bdt class=\"block-component\"></bdt><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"><bdt class=\"statement-end-if-in-editor\"><bdt class=\"block-component\"></bdt></bdt></span></span></span></span><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"><bdt class=\"block-component\"></bdt></bdt></span></span></span></span></bdt></span></span><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"infouse\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">2. HOW DO WE USE YOUR INFORMATION?</span></strong></span></span></span></span></span></div><div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short:  </em></strong><em>We process your information for purposes based on legitimate business interests, the fulfillment of our contract with you, compliance with our legal obligations, and/or your consent.</em></span></span></span></span></span></span></div></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We use personal information collected via our <span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span> for a variety of business purposes described below. We process your personal information for these purposes in reliance on our legitimate business interests, in order to enter into or perform a contract with you, with your consent, and/or for compliance with our legal obligations. We indicate the specific processing grounds we rely on next to each purpose listed below.<bdt class=\"block-component\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We use the information we collect or receive:<bdt class=\"block-component\"></bdt></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>To facilitate account creation and logon process.</strong> If you choose to link your account with us to a third-party account (such as your Google or Facebook account), we use the information you allowed us to collect from those third parties to facilitate account creation and logon process for the performance of the contract.<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span> </span></span>See the section below headed \"<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><a data-custom-class=\"link\" href=\"#sociallogins\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\">HOW DO WE HANDLE YOUR SOCIAL LOGINS</span></span></a></span></span>\" for further information.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>To post testimonials.</strong> We post testimonials on our <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span> that may contain personal information. Prior to posting a testimonial, we will obtain your consent to use your name and the content of the testimonial. If you wish to update, or delete your testimonial, please contact us at <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt><bdt class=\"question\">benawadapps@gmail.com</bdt><bdt class=\"else-block\"></bdt></span></span></span> and be sure to include your name, testimonial location, and contact information.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Request feedback. </strong>We may use your information to request feedback and to contact you about your use of our <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span>.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>To enable user-to-user communications.</strong> We may use your information in order to enable user-to-user communications with each user's consent.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>To manage user accounts. </strong>We may use your information for the purposes of managing our account and keeping it in working order.<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></li></ul><div><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></li></ul><div><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></strong></span></span></span></li></ul><div><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></li></ul><p style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></li></ul><p style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></li></ul><p style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></li></ul><p style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><bdt class=\"block-component\"><span style=\"font-size: 15px;\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><bdt class=\"block-component\"><span style=\"font-size: 15px;\"></bdt></span></span></span></li></ul><div><bdt class=\"block-component\"><span style=\"font-size: 15px;\"></bdt></span></span></span></li></ul><div><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px;\"></span></bdt></div><div style=\"line-height: 1.5;\"><br></div><div id=\"infoshare\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">3. WILL YOUR INFORMATION BE SHARED WITH ANYONE?</span></strong></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short:</em></strong><em>  We only share information with your consent, to comply with laws, to provide you with services, to protect your rights, or to fulfill business obligations.</em></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We may process or share your data that we hold based on the following legal basis:<bdt class=\"block-component\"></bdt></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Consent:</strong> We may process your data if you have given us specific consent to use your personal information for a specific purpose.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></bdt></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Legitimate Interests:</strong> We may process your data when it is reasonably necessary to achieve our legitimate business interests.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Performance of a Contract:</strong> Where we have entered into a contract with you, we may process your personal information to fulfill the terms of our contract.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Legal Obligations:</strong> We may disclose your information where we are legally required to do so in order to comply with applicable law, governmental requests, a judicial proceeding, court order, or legal process, such as in response to a court order or a subpoena (including in response to public authorities to meet national security or law enforcement requirements).<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Vital Interests:</strong> We may disclose your information where we believe it is necessary to investigate, prevent, or take action regarding potential violations of our policies, suspected fraud, situations involving potential threats to the safety of any person and illegal activities, or as evidence in litigation in which we are involved.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">More specifically, we may need to process your data or share your personal information in the following situations:</span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Business Transfers.</strong> We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company.</span></span></span></li></ul><div><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></bdt></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></div><div><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"><span data-custom-class=\"heading_1\"></bdt></span><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"sociallogins\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">4. HOW DO WE HANDLE YOUR SOCIAL LOGINS?</span></strong> </span> </span> </span> </span> </span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short:  </em></strong><em>If you choose to register or log in to our services using a social media account, we may have access to certain information about you.</em></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Our <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span> offers you the ability to register and log in using your third-party social media account details (like your Facebook or Twitter logins). Where you choose to do this, we will receive certain profile information about you from your social media provider. The profile information we receive may vary depending on the social media provider concerned, but will often include your name, email address, friends list, profile picture as well as other information you choose to make public on such social media platform. <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></span></bdt></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We will use the information we receive only for the purposes that are described in this privacy notice or that are otherwise made clear to you on the relevant <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span>. Please note that we do not control, and are not responsible for, other uses of your personal information by your third-party social media provider. We recommend that you review their privacy notice to understand how they collect, use and share your personal information, and how you can set your privacy preferences on their sites and apps.<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span><bdt class=\"block-component\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></span></bdt></span></span></span></span></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"inforetain\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">5. HOW LONG DO WE KEEP YOUR INFORMATION?</span></strong></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short: </em></strong><em> We keep your information for as long as necessary to fulfill the purposes outlined in this privacy notice unless otherwise required by law.</em></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We will only keep your personal information for as long as it is necessary for the purposes set out in this privacy notice, unless a longer retention period is required or permitted by law (such as tax, accounting or other legal requirements). No purpose in this notice will require us keeping your personal information for longer than <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span><bdt class=\"block-component\"></bdt>the period of time in which users have an account with us<bdt class=\"block-component\"></bdt><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"else-block\"></bdt></span></span></span>.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">When we have no ongoing legitimate business need to process your personal information, we will either delete or anonymize such information, or, if this is not possible (for example, because your personal information has been stored in backup archives), then we will securely store your personal information and isolate it from any further processing until deletion is possible.<span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"infosafe\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">6. HOW DO WE KEEP YOUR INFORMATION SAFE?</span></strong></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short: </em></strong><em> We aim to protect your personal information through a system of organizational and technical security measures.</em></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We have implemented appropriate technical and organizational security measures designed to protect the security of any personal information we process. However, despite our safeguards and efforts to secure your information, no electronic transmission over the Internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorized third parties will not be able to defeat our security, and improperly collect, access, steal, or modify your information. Although we will do our best to protect your personal information, transmission of personal information to and from our <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span> is at your own risk. You should only access the <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span> within a secure environment.<span style=\"color: rgb(89, 89, 89);\"><bdt class=\"statement-end-if-in-editor\"></bdt></span><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"privacyrights\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">7. WHAT ARE YOUR PRIVACY RIGHTS?</span></strong></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short:</em></strong><em>  <span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><em><bdt class=\"block-component\"></bdt></em></span></span></span>In some regions, such as the European Economic Area, you have rights that allow you greater access to and control over your personal information. <span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><em><bdt class=\"statement-end-if-in-editor\"></bdt></em></span></span></span>You may review, change, or terminate your account at any time.</em><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">In some regions (like the European Economic Area), you have certain rights under applicable data protection laws. These may include the right (i) to request access and obtain a copy of your personal information, (ii) to request rectification or erasure; (iii) to restrict the processing of your personal information; and (iv) if applicable, to data portability. In certain circumstances, you may also have the right to object to the processing of your personal information. To make such a request, please use the <a data-custom-class=\"link\" href=\"#contact\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(48, 48, 241);\">contact details</span></span></a> provided below. We will consider and act upon any request in accordance with applicable data protection laws.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If we are relying on your consent to process your personal information, you have the right to withdraw your consent at any time. Please note however that this will not affect the lawfulness of the processing before its withdrawal, nor will it affect the processing of your personal information conducted in reliance on lawful processing grounds other than consent.<span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"> </span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you are a resident in the European Economic Area and you believe we are unlawfully processing your personal information, you also have the right to complain to your local data protection supervisory authority. You can find their contact details here: <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(48, 48, 241);\"><span data-custom-class=\"body_text\"><a data-custom-class=\"link\" href=\"http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm\" rel=\"noopener noreferrer\" target=\"_blank\"><span style=\"font-size: 15px;\">http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm</span></a></span></span></span></span></span>.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you are a resident in Switzerland, the contact details for the data protection authorities are available here: <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(48, 48, 241);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><a data-custom-class=\"link\" href=\"https://www.edoeb.admin.ch/edoeb/en/home.html\" rel=\"noopener noreferrer\" target=\"_blank\">https://www.edoeb.admin.ch/edoeb/en/home.html</a></span></span></span></span></span></span>.<bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you have questions or comments about your privacy rights, you may email us at <span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"question\">benawadapps@gmail.com</bdt></span></span></span>.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span data-custom-class=\"heading_2\" style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><strong><u><br></u></strong><strong>Account Information</strong></span></span></div><div><div><br></div><div style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you would at any time like to review or change the information in your account or terminate your account, you can:<bdt class=\"forloop-component\"></bdt></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"question\">Contact us using the contact information provided.</bdt></span></span></span></span></span></span></li></ul><div><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"forloop-component\"></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Upon your request to terminate your account, we will deactivate or delete your account and information from our active databases. However, we may retain some information in our files to prevent fraud, troubleshoot problems, assist with any investigations, enforce our Terms of Use and/or comply with applicable legal requirements.<span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><u>Opting out of email marketing:</u></strong> You can unsubscribe from our marketing email list at any time by clicking on the unsubscribe link in the emails that we send or by contacting us using the details provided below. You will then be removed from the marketing email list — however, we may still communicate with you, for example to send you service-related emails that are necessary for the administration and use of your account, to respond to service requests, or for other non-marketing purposes. To otherwise opt-out, you may:<span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"forloop-component\"></bdt></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><bdt class=\"question\"><span data-custom-class=\"body_text\">Contact us using the contact information provided.</span></bdt></li></ul><div><bdt class=\"forloop-component\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></bdt></div><div style=\"line-height: 1.5;\"><br></div><div id=\"DNT\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">8. CONTROLS FOR DO-NOT-TRACK FEATURES</span></strong></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Most web browsers and some mobile operating systems and mobile applications include a Do-Not-Track (\"DNT\") feature or setting you can activate to signal your privacy preference not to have data about your online browsing activities monitored and collected. At this stage no uniform technology standard for recognizing and implementing DNT signals has been finalized. As such, we do not currently respond to DNT browser signals or any other mechanism that automatically communicates your choice not to be tracked online. If a standard for online tracking is adopted that we must follow in the future, we will inform you about that practice in a revised version of this privacy notice.</span></span> </span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"caresidents\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">9. DO CALIFORNIA RESIDENTS HAVE SPECIFIC PRIVACY RIGHTS?</span></strong></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong><em>In Short: </em></strong><em> Yes, if you are a resident of California, you are granted specific rights regarding access to your personal information.</em></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">California Civil Code Section 1798.83, also known as the \"Shine The Light\" law, permits our users who are California residents to request and obtain from us, once a year and free of charge, information about categories of personal information (if any) we disclosed to third parties for direct marketing purposes and the names and addresses of all third parties with which we shared personal information in the immediately preceding calendar year. If you are a California resident and would like to make such a request, please submit your request in writing to us using the contact information provided below.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you are under 18 years of age, reside in California, and have a registered account with <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>the Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span>, you have the right to request removal of unwanted data that you publicly post on the <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span>. To request removal of such data, please contact us using the contact information provided below, and include the email address associated with your account and a statement that you reside in California. We will make sure the data is not publicly displayed on the <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>Website<bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span>, but please be aware that the data may not be completely or comprehensively removed from all our systems (e.g. backups, etc.).<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><span data-custom-class=\"heading_2\" style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><strong><u><br></u></strong><strong>CCPA Privacy Notice</strong></span></span></div><div><div><br></div><div style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">The California Code of Regulations defines a \"resident\" as:</span></span></span></span></span></span></div></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5; margin-left: 20px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">(1) every individual who is in the State of California for other than a temporary or transitory purpose and</span></span></span></div><div style=\"line-height: 1.5; margin-left: 20px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">(2) every individual who is domiciled in the State of California who is outside the State of California for a temporary or transitory purpose</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">All other individuals are defined as \"non-residents.\"</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If this definition of \"resident\" applies to you, we must adhere to certain rights and obligations regarding your personal information.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>What categories of personal information do we collect?</strong></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We have collected the following categories of personal information in the past twelve (12) months:<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><table style=\"width: 100%;\"><tbody><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><br><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Category</strong></span></span></span><br><br></td><td style=\"width: 51.4385%; border-top: 1px solid black; border-right: 1px solid black;\"><br><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Examples</strong></span></span></span><br><br></td><td style=\"width: 14.9084%; border-right: 1px solid black; border-top: 1px solid black; text-align: center;\"><br><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Collected</strong></span></span></span><br><br></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">A. Identifiers</span></span></span></div></td><td style=\"width: 51.4385%; border-top: 1px solid black; border-right: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Contact details, such as real name, alias, postal address, telephone or mobile contact number, unique personal identifier, online identifier, Internet Protocol address, email address and account name</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; vertical-align: middle; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>YES<bdt class=\"else-block\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">B. Personal information categories listed in the California Customer Records statute</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Name, contact information, education, employment, employment history and financial information</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">YES</span></span></span></div><div style=\"line-height: 1.5;\"><br></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">C. Protected classification characteristics under California or federal law</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Gender and date of birth</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">D. Commercial information</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Transaction information, purchase history, financial details and payment information</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">E. Biometric information</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Fingerprints and voiceprints</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">F. Internet or other similar network activity</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Browsing history, search history, online behavior, interest data, and interactions with our and other websites, applications, systems and advertisements</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><br></span></span></span></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">G. Geolocation data</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Device location</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div><div style=\"line-height: 1.5;\"><br></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">H. Audio, electronic, visual, thermal, olfactory, or similar information</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Images and audio, video or call recordings created in connection with our business activities</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>YES<bdt class=\"else-block\"></bdt></span></span></span></div></td></tr><tr><td style=\"width: 33.8274%; border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">I. Professional or employment-related information</span></span></span></div></td><td style=\"width: 51.4385%; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Business contact details in order to provide you our services at a business level, job title as well as work history and professional qualifications if you apply for a job with us</span></span></span></div></td><td style=\"width: 14.9084%; text-align: center; border-right: 1px solid black; border-top: 1px solid black;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div></td></tr><tr><td style=\"border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black; width: 33.8274%;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">J. Education Information</span></span></span></div></td><td style=\"border-right: 1px solid black; border-top: 1px solid black; width: 51.4385%;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Student records and directory information</span></span></span></div></td><td style=\"text-align: center; border-right: 1px solid black; border-top: 1px solid black; width: 14.9084%;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div></td></tr><tr><td style=\"border-width: 1px; border-color: black; border-style: solid; width: 33.8274%;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">K. Inferences drawn from other personal information</span></span></span></div></td><td style=\"border-bottom: 1px solid black; border-top: 1px solid black; border-right: 1px solid black; width: 51.4385%;\"><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Inferences drawn from any of the collected personal information listed above to create a profile or summary about, for example, an individual’s preferences and characteristics</span></span></span></div></td><td style=\"text-align: center; border-right: 1px solid black; border-bottom: 1px solid black; border-top: 1px solid black; width: 14.9084%;\"><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>NO<bdt class=\"statement-end-if-in-editor\"><span data-custom-class=\"body_text\"></span></bdt></span></span></span></div></td></tr></tbody></table><div style=\"line-height: 1.5;\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We may also collect other personal information outside of these categories instances where you interact with us in-person, online, or by phone or mail in the context of:<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Receiving help through our customer support channels;</span></span><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Participation in customer surveys or contests; and</span></span><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Facilitation in the delivery of our Services and to respond to your inquiries.</span></span><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>How do we use and share your personal information?</strong></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">More information about our data collection and sharing practices can be found in this privacy notice<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span>.<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">You may contact us <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span>by email at </span><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"question\">benawadapps@gmail.com</bdt>, <bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"><span data-custom-class=\"body_text\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span><span data-custom-class=\"body_text\">or by referring to the contact details at the bottom of this document.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you are using an authorized agent to exercise your right to opt-out we may deny a request if the authorized agent does not submit proof that they have been validly authorized to act on your behalf.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Will your information be shared with anyone else?</strong></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We may disclose your personal information with our service providers pursuant to a written contract between us and each service provider. Each service provider is a for-profit entity that processes the information on our behalf.<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We may use your personal information for our own business purposes, such as for undertaking internal research for technological development and demonstration. This is not considered to be \"selling\" of your personal data.<span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"question\">DogeHouse</bdt></span><span data-custom-class=\"body_text\"> has not disclosed or sold any personal information to third parties for a business or commercial purpose in the preceding 12 months. <span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"question\">DogeHouse</bdt></span></span></span> will not sell personal information in the future belonging to website visitors, users and other consumers.<span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></bdt></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span><span data-custom-class=\"body_text\"><bdt class=\"block-component\"><span style=\"color: rgb(0, 0, 0);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><strong>Your rights with respect to your personal data</strong></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><u>Right to request deletion of the data - Request to delete</u></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">You can ask for the deletion of your personal information. If you ask us to delete your personal information, we will respect your request and delete your personal information, subject to certain exceptions provided by law, such as (but not limited to) the exercise by another consumer of his or her right to free speech, our compliance requirements resulting from a legal obligation or any processing that may be required to protect against illegal activities.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><u>Right to be informed - Request to know</u></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Depending on the circumstances, you have a right to know:<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">whether we collect and use your personal information;<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">the categories of personal information that we collect;<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">the purposes for which the collected personal information is used;<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">whether we sell your personal information to third parties;<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">the categories of personal information that we sold or disclosed for a business purpose;<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">the categories of third parties to whom the personal information was sold or disclosed for a business purpose; and<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">the business or commercial purpose for collecting or selling personal information.<span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">In accordance with applicable law, we are not obligated to provide or delete consumer information that is de-identified in response to a consumer request or to re-identify individual data to verify a consumer request.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><u>Right to Non-Discrimination for the Exercise of a Consumer’s Privacy Rights</u></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We will not discriminate against you if you exercise your privacy rights.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><u>Verification process</u></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Upon receiving your request, we will need to verify your identity to determine you are the same person about whom we have the information in our system. These verification efforts require us to ask you to provide information so that we can match it with information you have previously provided us. For instance, depending on the type of request you submit, we may ask you to provide certain information so that we can match the information you provide with the information we already have on file, or we may contact you through a communication method (e.g. phone or email) that you have previously provided to us. We may also use other verification methods as the circumstances dictate.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We will only use personal information provided in your request to verify your identity or authority to make the request. To the extent possible, we will avoid requesting additional information from you for the purposes of verification. If, however, we cannot verify your identity from the information already maintained by us, we may request that you provide additional information for the purposes of verifying your identity, and for security or fraud-prevention purposes. We will delete such additionally provided information as soon as we finish verifying you.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><u>Other privacy rights</u></span></span></span></div><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">you may object to the processing of your personal data<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">you may request correction of your personal data if it is incorrect or no longer relevant, or ask to restrict the processing of the data<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">you can designate an authorized agent to make a request under the CCPA on your behalf. We may deny a request from an authorized agent that does not submit proof that they have been validly authorized to act on your behalf in accordance with the CCPA.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span></span></span></span></span></span></span></div><ul><li style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">you may request to opt-out from future selling of your personal information to third parties. Upon receiving a request to opt-out, we will act upon the request as soon as feasibly possible, but no later than 15 days from the date of the request submission.<span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(0, 0, 0);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span></li></ul><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">To exercise these rights, you can contact us </span><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>by email at <bdt class=\"question\">benawadapps@gmail.com</bdt>, <bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"><span data-custom-class=\"body_text\"></bdt></span></span></span></span></span></span></span></span></span></span></span></span><span data-custom-class=\"body_text\">or by referring to the contact details at the bottom of this document. If you have a complaint about how we handle your data, we would like to hear from you.</span><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></span></span></span><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"statement-end-if-in-editor\"><span data-custom-class=\"body_text\"></span></bdt></span></span></span></span></span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"policyupdates\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">10. DO WE MAKE UPDATES TO THIS NOTICE?</span></strong> </span> </span> </span> </span> </span></div><div style=\"line-height: 1.5;\"><em><br></em></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><em><strong>In Short: </strong> Yes, we will update this notice as necessary to stay compliant with relevant laws.</em></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">We may update this privacy notice from time to time. The updated version will be indicated by an updated \"Revised\" date and the updated version will be effective as soon as it is accessible. If we make material changes to this privacy notice, we may notify you either by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this privacy notice frequently to be informed of how we are protecting your information.</span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"contact\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">11. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?</span></strong> </span> </span> </span> </span> </span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">If you have questions or comments about this notice, you may <span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt>email us at <bdt class=\"question\">benawadapps@gmail.com</bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></span><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"> or by post to:</span></span></span></span></span></span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><span style=\"font-size: 15px;\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"question\">DogeHouse</bdt></span></span></span></span></span><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"></bdt></span></span></bdt></span></span></span></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"question\">6705 West Highway 290</bdt><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></span></span></span></div><div style=\"line-height: 1.5;\"><span data-custom-class=\"body_text\" style=\"font-size: 15px;\"><bdt class=\"question\">Suite 50230<span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></bdt></span></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"question\">Austin</bdt><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt>, <bdt class=\"question\">TX</bdt><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt> <bdt class=\"question\">78735</bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></span></span></span></div><div style=\"line-height: 1.5;\"><span data-custom-class=\"body_text\" style=\"font-size: 15px;\"><bdt class=\"question\">United States<span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span><bdt class=\"else-block\"></bdt></span></span></span></bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span><span data-custom-class=\"body_text\"><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span><span style=\"color: rgb(89, 89, 89);\"><span style=\"font-size: 15px;\"><span data-custom-class=\"body_text\"><bdt class=\"block-component\"><bdt class=\"block-component\"></bdt></span></span></div><div style=\"line-height: 1.5;\"><br></div><div id=\"request\" style=\"line-height: 1.5;\"><span style=\"color: rgb(127, 127, 127);\"><span style=\"color: rgb(89, 89, 89); font-size: 15px;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span id=\"control\" style=\"color: rgb(0, 0, 0);\"><strong><span data-custom-class=\"heading_1\">12. HOW CAN YOU REVIEW, UPDATE, OR DELETE THE DATA WE COLLECT FROM YOU?</span></strong> </span> </span> </span> </span> </span></div><div style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span style=\"font-size: 15px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">Based on the applicable laws of your country, you may have the right to request access to the personal information we collect from you, change that information, or delete it in some circumstances. To request to review, update, or delete your personal information, please <bdt class=\"block-component\"></span></bdt><span data-custom-class=\"body_text\">visit: <bdt class=\"question\"><a href=\"https://dogehouse.tv\" target=\"_blank\" data-custom-class=\"link\">https://dogehouse.tv</a></bdt><bdt class=\"else-block\"></bdt></span></span><span data-custom-class=\"body_text\">. We will respond to your request within 30 days.</span></span></span></div><style>\n        ul {\n          list-style-type: square;\n        }\n        ul > li > ul {\n          list-style-type: circle;\n        }\n        ul > li > ul > li > ul {\n          list-style-type: square;\n        }\n        ol li {\n          font-family: Arial ;\n        }\n      </style>\n        </div>"
  },
  {
    "path": "kibbeh/public/terms.html",
    "content": "<style>\n    [data-custom-class='body'], [data-custom-class='body'] * {\n            background: transparent !important;\n          }\n  [data-custom-class='title'], [data-custom-class='title'] * {\n            font-family: Arial !important;\n  font-size: 26px !important;\n  color: #000000 !important;\n          }\n  [data-custom-class='subtitle'], [data-custom-class='subtitle'] * {\n            font-family: Arial !important;\n  color: #595959 !important;\n  font-size: 14px !important;\n          }\n  [data-custom-class='heading_1'], [data-custom-class='heading_1'] * {\n            font-family: Arial !important;\n  font-size: 19px !important;\n  color: #000000 !important;\n          }\n  [data-custom-class='heading_2'], [data-custom-class='heading_2'] * {\n            font-family: Arial !important;\n  font-size: 17px !important;\n  color: #000000 !important;\n          }\n  [data-custom-class='body_text'], [data-custom-class='body_text'] * {\n            color: #595959 !important;\n  font-size: 14px !important;\n  font-family: Arial !important;\n          }\n  [data-custom-class='link'], [data-custom-class='link'] * {\n            color: #3030F1 !important;\n  font-size: 14px !important;\n  font-family: Arial !important;\n  word-break: break-word !important;\n          }\n  </style>\n\n        <div data-custom-class=\"body\">\n        <div align=\"center\" style=\"text-align: left; line-height: 1;\"><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div align=\"center\" class=\"MsoNormal\" style=\"text-align:center;line-height:150%;\"><a name=\"_2cipo4yr3w5d\"></a><div align=\"center\" class=\"MsoNormal\" style=\"line-height: 22.5px;\"><div align=\"center\" class=\"MsoNormal\" style=\"line-height: 150%;\"><a name=\"_gm5sejt4p02f\"></a><div align=\"center\" class=\"MsoNormal\" data-custom-class=\"title\" style=\"text-align: left; line-height: 1.5;\"><strong><span style=\"line-height: 22.5px; font-size: 26px;\">TERMS OF USE</span></strong></div><div align=\"center\" class=\"MsoNormal\" style=\"line-height: 22.5px; text-align: left;\"><a name=\"_7m5b3xg56u7y\"></a></div><div align=\"center\" class=\"MsoNormal\" data-custom-class=\"subtitle\" style=\"text-align: left; line-height: 22.5px;\"><br></div><div align=\"center\" class=\"MsoNormal\" data-custom-class=\"subtitle\" style=\"text-align: left; line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px; text-align: justify;\"><strong>Last updated <bdt class=\"block-container question question-in-editor\" data-id=\"e2088df5-25ea-aec9-83d4-6284f5a7e043\" data-type=\"question\">February 17, 2021</bdt></strong></span></div></div></div><div align=\"center\" class=\"MsoNormal\" style=\"line-height: 17.25px; text-align: left;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"line-height: 17.25px; text-align: left;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"line-height: 17.25px; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px;\"><br></span></div></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><a name=\"_a7mwfgcrtsqn\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">AGREEMENT TO TERMS</span></strong></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\">These Terms of Use constitute a legally binding agreement made between you, whether personally or on behalf of an entity (“you”) and <bdt class=\"block-container question question-in-editor\" data-id=\"4ab94aa9-19d1-61e0-711e-42c7d186232b\" data-type=\"question\">DogeHouse</bdt><bdt class=\"block-component\"></bdt> (\"<bdt class=\"block-component\"></bdt><strong>Company</strong><bdt class=\"statement-end-if-in-editor\"></bdt>\", “<strong>we</strong>”, “<strong>us</strong>”, or “<strong>our</strong>”), concerning your access to and use of the <bdt class=\"block-container question question-in-editor\" data-id=\"92c3b320-1d8b-c74c-db68-d12810736807\" data-type=\"question\"><a href=\"https://dogehouse.tv\" target=\"_blank\" data-custom-class=\"link\">https://dogehouse.tv</a></bdt> website as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (collectively, the “Site”). You agree that by accessing the Site, you have read, understood, and agreed to be bound by all of these Terms of Use. IF YOU DO NOT AGREE WITH ALL OF THESE TERMS OF USE, THEN YOU ARE EXPRESSLY PROHIBITED FROM USING THE SITE AND YOU MUST DISCONTINUE USE IMMEDIATELY.</span></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\">Supplemental terms and conditions or documents that may be posted on the Site from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Terms of Use at any time and for any reason. We will alert you about any changes by updating the “Last updated” date of these Terms of Use, and you waive any right to receive specific notice of each such change. It is your responsibility to periodically review these Terms of Use to stay informed of updates. You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Terms of Use by your continued use of the Site after the date such revised Terms of Use are posted.</span></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\">The information provided on the Site is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. Accordingly, those persons who choose to access the Site from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable.</span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\"><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></bdt></span></span></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_o18_option\" data-type=\"statement\"><span style=\"font-size: 15px;\"></bdt><bdt data-type=\"body\"><span style=\"color: rgb(89, 89, 89);\">The Site is intended for users who are at least 13 years of age. All users who are minors in the jurisdiction in which they reside (generally under the age of 18) must have the permission of, and be directly supervised by, their parent or guardian to use the Site. If you are a minor, you must have your parent or guardian read and agree to these Terms of Use prior to you using the Site.</span></bdt></bdt><bdt data-type=\"conditional-block\"> <bdt class=\"block-container if\" data-type=\"if\" id=\"a2595956-7028-dbe5-123e-d3d3a93ed076\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\"></span></bdt></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_4rd71iod99ud\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">INTELLECTUAL PROPERTY RIGHTS</span></strong></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\">Unless otherwise indicated, the Site is our proprietary\n  property and all source code, databases, functionality, software, website\n  designs, audio, video, text, photographs, and graphics on the Site\n  (collectively, the “Content”) and the trademarks, service marks, and logos\n  contained therein (the “Marks”) are owned or controlled by us or licensed to\n  us, and are protected by copyright and trademark laws and various other\n  intellectual property rights and unfair competition laws of the United States, international copyright laws, and international conventions. The Content and the Marks are provided on the\n  Site “AS IS” for your information and personal use only. Except as expressly provided in these Terms\n  of Use, no part of the Site and no Content or Marks may be copied, reproduced,\n  aggregated, republished, uploaded, posted, publicly displayed, encoded,\n  translated, transmitted, distributed, sold, licensed, or otherwise exploited\n  for any commercial purpose whatsoever, without our express prior written\n  permission.</span></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;\n  Arial;mso-fareast-font-family:Calibri;color:#595959;mso-themecolor:text1;\n  mso-themetint:166;\">Provided that you are eligible to use the Site, you are\n  granted a limited license to access and use the Site and to download or print a\n  copy of any portion of the Content to which you have properly gained access\n  solely for your personal, non-commercial use. We reserve all rights not\n  expressly granted to you in and to the Site, the Content and the Marks.</span></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_vhkegautf00d\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">USER REPRESENTATIONS</span></strong></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" style=\"text-align:justify;text-justify:inter-ideograph;\n  line-height:115%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">By using the Site, you represent and warrant that: </span><bdt class=\"block-container if\" data-type=\"if\" id=\"d2d82ca8-275f-3f86-8149-8a5ef8054af6\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_account_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\"><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">(</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">1</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) all registration information you submit will be true, accurate, current, and complete; (</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">2</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) you will maintain the accuracy of such information and promptly update such registration information as necessary<bdt class=\"block-container if\" data-type=\"if\" id=\"d2d82ca8-275f-3f86-8149-8a5ef8054af6\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">;</span></bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt> </bdt><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">(</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">3</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) you have the legal capacity and you agree to comply with these Terms of Use;</span><bdt class=\"block-container if\" data-type=\"if\" id=\"8d4c883b-bc2c-f0b4-da3e-6d0ee51aca13\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_u13_option\" data-type=\"statement\"></bdt> <bdt data-type=\"body\"><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">(</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">4</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) you are not under the age of 13;</span></bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt></span></bdt></bdt></bdt><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\"> (</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">5</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) you are not a minor in the jurisdiction in which you reside<bdt class=\"block-container if\" data-type=\"if\" id=\"76948fab-ec9e-266a-bb91-948929c050c9\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_o18_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\">, or if a minor, you have received parental permission to use the Site</bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt>; (</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">6</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) you will not access the Site through automated or non-human means, whether through a bot, script, or otherwise; (</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">7</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) you will not use the Site for any illegal or unauthorized purpose; and (</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">8</span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">) your use of the Site will not violate any applicable law or regulation.</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\"></span></div></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align:justify;text-justify:inter-ideograph;\n  line-height:115%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">If you provide any information that is untrue, inaccurate, not current, or incomplete, we have the right to suspend or terminate your account and refuse any and all current or future use of the Site (or any portion thereof).</span></div></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><a name=\"_esuoutkhaf53\"></a> <bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_account_option\" data-type=\"statement\"><span style=\"font-size: 15px;\"></span></bdt><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 17.25px;\"><strong><span style=\"line-height: 24.5333px; font-size: 19px;\">USER REGISTRATION</span></strong></div></bdt></bdt></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">You may be required to register with the Site. You agree to keep your password confidential and will be responsible for all use of your account and password. We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that such username is inappropriate, obscene, or otherwise objectionable.</span></div></bdt></bdt></div></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" class=\"MsoNormal\" style=\"text-align: left; line-height: 150%;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"><span style=\"font-size: 15px;\"></span></bdt></div><div class=\"MsoNormal\" style=\"line-height:115%;\"><a name=\"_1voziltdxegg\"></a><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 17.25px;\"><strong><span style=\"line-height: 24.5333px; font-size: 19px;\">PROHIBITED ACTIVITIES</span></strong></div></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"line-height:115%;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">You may not access or use the Site for any purpose other than that for which we make the Site available. The Site may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us.</span></div></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"line-height:115%;\"><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 17.25px;\"><div class=\"MsoNormal\" style=\"line-height: 17.25px;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">As a user of the Site, you agree not to:</span></div><div class=\"MsoNormal\" style=\"line-height: 17.25px;\"><span style=\"font-size: 15px; line-height: 16.8667px; color: rgb(89, 89, 89);\"><div class=\"MsoNormal\" style=\"color: rgb(10, 54, 90); font-size: 15px; line-height: 1; text-align: left;\"><br></div></span></div></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">1</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. </span></span>Systematically retrieve data or other content from the Site to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us.<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">2</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">3</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Circumvent, disable, or otherwise interfere with security-related features of the Site, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Site and/or the Content contained therein.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">4</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Site.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">5</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Use any information obtained from the Site in order to harass, abuse, or harm another person.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">6</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Make improper use of our support services or submit false reports of abuse or misconduct.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">7</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Use the Site in a manner inconsistent with any applicable laws or regulations.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">8</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party’s uninterrupted use and enjoyment of the Site or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Site.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">9</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats (“gifs”), 1×1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as “spyware” or “passive collection mechanisms” or “pcms”).</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">10</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Interfere with, disrupt, or create an undue burden on the Site or the networks or services connected to the Site.</span></span></span></span></span><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px;\"></span></bdt></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">11</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Site to you.</span></span></span></span></span><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px;\"></span></bdt></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">12</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Attempt to bypass any measures of the Site designed to prevent or restrict access to the Site, or any portion of the Site.</span></span></span></span></span><bdt class=\"statement-end-if-in-editor\"><span style=\"font-size: 15px;\"></span></bdt></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></span></bdt></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></span></bdt></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><span style=\"line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -22.05pt; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\"><bdt class=\"block-container forloop\" data-type=\"forloop\" id=\"19beb913-5a5e-2b51-51f9-8600a8eb26c3\" style=\"display: inline;\"><bdt data-type=\"conditional-block\" style=\"display: inline;\"><bdt data-type=\"body\" style=\"display: inline;\">13</bdt></bdt></bdt></span><span style=\"font-family: sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: -29.4px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; color: rgb(89, 89, 89);\">. Use the Site as part of any effort to compete with us or otherwise use the Site and/or the Content for any revenue-generating endeavor or commercial enterprise.</span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 15px;\"><bdt class=\"forloop-component\"></bdt></span></div><div class=\"MsoNormal\" style=\"text-align: left; line-height: 1.5;\"><a name=\"_zbbv9tgty199\"></a></div></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 17.25px; text-align: left;\"><strong><span style=\"line-height: 24.5333px; font-size: 19px;\">USER GENERATED CONTRIBUTIONS</span></strong> </div></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt>The Site may invite you to chat, contribute to, or participate in blogs, message boards, online forums, and other functionality, and may provide you with the opportunity to create, submit, post, display, transmit, perform, publish, distribute, or broadcast content and materials to us or on the Site, including but not limited to text, writings, video, audio, photographs, graphics, comments, suggestions, or personal information or other material (collectively, \"Contributions\"). Contributions may be viewable by other users of the Site and through third-party websites. As such, any Contributions you transmit may be treated as non-confidential and non-proprietary. When you create or make available any Contributions, you thereby represent and warrant that:<bdt class=\"else-block\"></bdt></span></span></div></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" style=\"line-height: 1; text-align: left;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 17.25px;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; margin-left: 20px; text-align: left;\"><span style=\"font-size: 14px; color: rgb(89, 89, 89);\"><span data-custom-class=\"body_text\">1.  The creation, distribution, transmission, public display, or performance, and the accessing, downloading, or copying of your Contributions do not and will not infringe the proprietary rights, including but not limited to the copyright, patent, trademark, trade secret, or moral rights of any third party.<br>2.  You are the creator and owner of or have the necessary licenses, rights, consents, releases, and permissions to use and to authorize us, the Site, and other users of the Site to use your Contributions in any manner contemplated by the Site and these Terms of Use.<br>3.  You have the written consent, release, and/or permission of each and every identifiable individual person in your Contributions to use the name or likeness of each and every such identifiable individual person to enable inclusion and use of your Contributions in any manner contemplated by the Site and these Terms of Use.<br>4.  Your Contributions are not false, inaccurate, or misleading.<br>5.  Your Contributions are not unsolicited or unauthorized advertising, promotional materials, pyramid schemes, chain letters, spam, mass mailings, or other forms of solicitation.<br>6.  Your Contributions are not obscene, lewd, lascivious, filthy, violent, harassing, libelous, slanderous, or otherwise objectionable (as determined by us).<br>7.  Your Contributions do not ridicule, mock, disparage, intimidate, or abuse anyone.<br>8.  Your Contributions are not used to harass or threaten (in the legal sense of those terms) any other person and to promote violence against a specific person or class of people.<br>9.  Your Contributions do not violate any applicable law, regulation, or rule.<br>10.  Your Contributions do not violate the privacy or publicity rights of any third party.<br>11.  Your Contributions do not contain any material that solicits personal information from anyone under the age of 18 or exploits people under the age of 18 in a sexual or violent manner.<br>12.  Your Contributions do not violate any applicable law concerning child pornography, or otherwise intended to protect the health or well-being of minors.<br>13.  Your Contributions do not include any offensive comments that are connected to race, national origin, gender, sexual preference, or physical handicap.<br>14.  Your Contributions do not otherwise violate, or link to material that violates, any provision of these Terms of Use, or any applicable law or regulation.</span></span></div><div class=\"MsoNormal\" style=\"line-height: 1; margin-left: 20px; text-align: left;\"><br></div></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">Any use of the Site in violation of the foregoing violates these Terms of Use and may result in, among other things, termination or suspension of your rights to use the Site.</span></div></bdt></bdt></div></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height:115%;\"><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5; text-align: left;\"><strong><span style=\"line-height: 24.5333px; font-size: 19px;\">CONTRIBUTION LICENSE</span></strong></div></bdt></bdt><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" style=\"line-height: 17.25px; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></div></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">By posting your Contributions to any part of the Site<bdt class=\"block-container if\" data-type=\"if\" id=\"19652acc-9a2a-5ffe-6189-9474402fa6cc\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"socialnetwork_link_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\"> or making Contributions accessible to the Site by linking your account from the Site to any of your social networking accounts</bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt>, you automatically grant, and you represent and warrant that you have the right to grant, to us an unrestricted, unlimited, irrevocable, perpetual, non-exclusive, transferable, royalty-free, fully-paid, worldwide right, and license to host, use, copy, reproduce, disclose, sell, resell, publish, broadcast, retitle, archive, store, cache, publicly perform, publicly display, reformat, translate, transmit, excerpt (in whole or in part), and distribute such Contributions (including, without limitation, your image and voice) for any purpose, commercial, advertising, or otherwise, and to prepare derivative works of, or incorporate into other works, such Contributions, and grant and authorize sublicenses of the foregoing. The use and distribution may occur in any media formats and through any media channels.</span></div></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">This license will apply to any form, media, or technology now known or hereafter developed, and includes our use of your name, company name, and franchise name, as applicable, and any of the trademarks, service marks, trade names, logos, and personal and commercial images you provide. You waive all moral rights in your Contributions, and you warrant that moral rights have not otherwise been asserted in your Contributions.</span></div></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">We do not assert any ownership over your Contributions. You retain full ownership of all of your Contributions and any intellectual property rights or other proprietary rights associated with your Contributions. We are not liable for any statements or representations in your Contributions provided by you in any area on the Site. You are solely responsible for your Contributions to the Site and you expressly agree to exonerate us from any and all responsibility and to refrain from any legal action against us regarding your Contributions.  </span></div></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">We have the right, in our sole and absolute discretion, (1) to edit, redact, or otherwise change any Contributions; (2) to re-categorize any Contributions to place them in more appropriate locations on the Site; and (3) to pre-screen or delete any Contributions at any time and for any reason, without notice. We have no obligation to monitor your Contributions.<bdt class=\"else-block\"></bdt></span></span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\" id=\"a378120a-96b1-6fa3-279f-63d5b96341d3\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"review_option\" data-type=\"statement\"><span style=\"font-size: 15px;\"></span></bdt> <bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">GUIDELINES FOR REVIEWS</span></strong></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">We may provide you areas on the Site to leave reviews or ratings. When posting a review, you must comply with the following criteria: (1) you should have firsthand experience with the person/entity being reviewed; (2) your reviews should not contain offensive profanity, or abusive, racist, offensive, or hate language; (3) your reviews should not contain discriminatory references based on religion, race, gender, national origin, age, marital status, sexual orientation, or disability; (4) your reviews should not contain references to illegal activity; (5) you should not be affiliated with competitors if posting negative reviews; (6) you should not make any conclusions as to the legality of conduct; (7) you may not post any false or misleading statements; and (8) you may not organize a campaign encouraging others to post reviews, whether positive or negative.</span></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5; text-align: left;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">We may accept, reject, or remove reviews in our sole discretion. We have absolutely no obligation to screen reviews or to delete reviews, even if anyone considers reviews objectionable or inaccurate. Reviews are not endorsed by us, and do not necessarily represent our opinions or the views of any of our affiliates or partners. We do not assume liability for any review or for any claims, liabilities, or losses resulting from any review. By posting a review, you hereby grant to us a perpetual, non-exclusive, worldwide, royalty-free, fully-paid, assignable, and sublicensable right and license to reproduce, modify, translate, transmit by any means, display, perform, and/or distribute all content relating to reviews.</span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"><span style=\"font-size: 15px;\"></span></bdt></bdt></div></div><div class=\"MsoNormal\" style=\"line-height: 115%;\"><span style=\"font-size: 15px;\"><a name=\"_6nl7u6ag6use\"></a></span></div><bdt class=\"block-container if\" data-type=\"if\" id=\"c954892f-02b9-c743-d1e8-faf0d59a4b70\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"mobile_app_option\" data-type=\"statement\"><span style=\"font-size: 15px;\"></span></bdt></bdt></div><div align=\"center\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">SOCIAL MEDIA</span></strong></div><div style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">As part of the functionality of the Site, you may link your account with online accounts you have with third-party service providers (each such account, a “Third-Party Account”) by either: (1) providing your Third-Party Account login information through the Site; or (2) allowing us to access your Third-Party Account, as is permitted under the applicable terms and conditions that govern your use of each Third-Party Account. You represent and warrant that you are entitled to disclose your Third-Party Account login information to us and/or grant us access to your Third-Party Account, without breach by you of any of the terms and conditions that govern your use of the applicable Third-Party Account, and without obligating us to pay any fees or making us subject to any usage limitations imposed by the third-party service provider of the Third-Party Account. By granting us access to any Third-Party Accounts, you understand that (1) we may access, make available, and store (if applicable) any content that you have provided to and stored in your Third-Party Account (the “Social Network Content”) so that it is available on and through the Site via your account, including without limitation any friend lists and (2) we may submit to and receive from your Third-Party Account additional information to the extent you are notified when you link your account with the Third-Party Account. Depending on the Third-Party Accounts you choose and subject to the privacy settings that you have set in such Third-Party Accounts, personally identifiable information that you post to your Third-Party Accounts may be available on and through your account on the Site. Please note that if a Third-Party Account or associated service becomes unavailable or our access to such Third Party Account is terminated by the third-party service provider, then Social Network Content may no longer be available on and through the Site. You will have the ability to disable the connection between your account on the Site and your Third-Party Accounts at any time. PLEASE NOTE THAT YOUR RELATIONSHIP WITH THE THIRD-PARTY SERVICE PROVIDERS ASSOCIATED WITH YOUR THIRD-PARTY ACCOUNTS IS GOVERNED SOLELY BY YOUR AGREEMENT(S) WITH SUCH THIRD-PARTY SERVICE PROVIDERS. We make no effort to review any Social Network Content for any purpose, including but not limited to, for accuracy, legality, or non-infringement, and we are not responsible for any Social Network Content. You acknowledge and agree that we may access your email address book associated with a Third-Party Account and your contacts list stored on your mobile device or tablet computer solely for purposes of identifying and informing you of those contacts who have also registered to use the Site. You can deactivate the connection between the Site and your Third-Party Account by contacting us using the contact information below or through your account settings (if applicable). We will attempt to delete any information stored on our servers that was obtained through such Third-Party Account, except the username and profile picture that become associated with your account.</span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div align=\"center\" style=\"line-height: 1.5;\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">SUBMISSIONS</span></strong></div><div style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">You acknowledge and agree that any questions, comments, suggestions, ideas, feedback, or other information regarding the Site (\"Submissions\") provided by you to us are non-confidential and shall become our sole property. We shall own exclusive rights, including all intellectual property rights, and shall be entitled to the unrestricted use and dissemination of these Submissions for any lawful purpose, commercial or otherwise, without acknowledgment or compensation to you. You hereby waive all moral rights to any such Submissions, and you hereby warrant that any such Submissions are original with you or that you have the right to submit such Submissions. You agree there shall be no recourse against us for any alleged or actual infringement or misappropriation of any proprietary right in your Submissions.</span></span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><br></div><div align=\"center\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">THIRD-PARTY WEBSITE AND CONTENT</span></strong></div><div style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">The Site may contain (or you may be sent via the Site) links to other websites (\"Third-Party Websites\") as well as articles, photographs, text, graphics, pictures, designs, music, sound, video, information, applications, software, and other content or items belonging to or originating from third parties (\"Third-Party Content\"). Such Third-Party Websites and Third-Party Content are not investigated, monitored, or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third-Party Websites accessed through the Site or any Third-Party Content posted on, available through, or installed from the Site, including the content, accuracy, offensiveness, opinions, reliability, privacy practices, or other policies of or contained in the Third-Party Websites or the Third-Party Content. Inclusion of, linking to, or permitting the use or installation of any Third-Party Websites or any Third-Party Content does not imply approval or endorsement thereof by us. If you decide to leave the Site and access the Third-Party Websites or to use or install any Third-Party Content, you do so at your own risk, and you should be aware these Terms of Use no longer govern. You should review the applicable terms and policies, including privacy and data gathering practices, of any website to which you navigate from the Site or relating to any applications you use or install from the Site. Any purchases you make through Third-Party Websites will be through other websites and from other companies, and we take no responsibility whatsoever in relation to such purchases which are exclusively between you and the applicable third party. You agree and acknowledge that we do not endorse the products or services offered on Third-Party Websites and you shall hold us harmless from any harm caused by your purchase of such products or services. Additionally, you shall hold us harmless from any losses sustained by you or harm caused to you relating to or resulting in any way from any Third-Party Content or any contact with Third-Party Websites.</span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div style=\"line-height: 1.5;\"><br></div><div align=\"center\"><span style=\"font-size: 15px;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><div class=\"MsoNormal\" style=\"line-height: 115%;\"><span style=\"font-size: 15px;\"><a name=\"_29ce8o9pbtmi\"></a></span></div><bdt class=\"block-container if\" data-type=\"if\" id=\"14038561-dad7-be9d-370f-f8aa487b2570\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"advertiser_option\" data-type=\"statement\"><span style=\"font-size: 15px;\"></span></bdt></bdt><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><a name=\"_wj13r09u8u3u\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">SITE MANAGEMENT</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">We reserve the\n  right, but not the obligation, to: (1) monitor the Site for violations of\n  these Terms of Use; (2) take appropriate legal action against anyone who, in\n  our sole discretion, violates the law or these Terms of Use, including without\n  limitation, reporting such user to law enforcement authorities; (3) in our sole\n  discretion and without limitation, refuse, restrict access to, limit the\n  availability of, or disable (to the extent technologically feasible) any of\n  your Contributions or any portion thereof; (4) in our sole discretion and\n  without limitation, notice, or liability, to remove from the Site or otherwise\n  disable all files and content that are excessive in size or are in any way\n  burdensome to our systems; and (5) otherwise manage the Site in a manner\n  designed to protect our rights and property and to facilitate the proper\n  functioning of the Site.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><a name=\"_jugvcvcw0oj9\"></a></div><bdt class=\"block-container if\" data-type=\"if\" id=\"bdd90fa9-e664-7d0b-c352-2b8e77dd3bb4\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"privacy_policy_option\" data-type=\"statement\"><span style=\"font-size: 15px;\"></span></bdt> <bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">PRIVACY POLICY</span></strong></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\"><br></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">We care about data privacy and security. </span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">Please review our Privacy Policy:</span><b style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\"> <bdt class=\"block-container question question-in-editor\" data-id=\"d10c7fd7-0685-12ac-c717-cbc45ff916d1\" data-type=\"question\"><a href=\"https://dogehouse.tv/privacy-policy\" target=\"_blank\" data-custom-class=\"link\">https://dogehouse.tv/privacy-policy</a></bdt></b><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\">. </span><span style=\"color: rgb(89, 89, 89); font-size: 11pt;\">By using the Site, you agree to be bound by our Privacy Policy, which is incorporated into these Terms of Use. Please be advised the Site is hosted in <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt>the <bdt class=\"question\">United States</bdt><bdt class=\"block-component\"></bdt></span>. If you access the Site from any other region of the world with laws or other requirements governing personal data collection, use, or disclosure that differ from applicable laws in <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt>the <bdt class=\"question\">United States</bdt><bdt class=\"block-component\"></bdt></span>, then through your continued use of the Site, you are transferring your data to <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt>the <bdt class=\"question\">United States</bdt><bdt class=\"block-component\"></bdt></span>, and you agree to have your data transferred to and processed in <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt>the <bdt class=\"question\">United States</bdt><bdt class=\"block-component\"></bdt></span>. <bdt class=\"block-component\"></bdt></span><bdt class=\"block-container if\" data-type=\"if\" id=\"547bb7bb-ecf2-84b9-1cbb-a861dc3e14e7\" style=\"color: rgb(89, 89, 89); font-size: 11pt;\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_u13_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\">Further, we do not knowingly accept, request, or solicit information from children or knowingly market to children. Therefore, in accordance with the U.S. Children’s Online Privacy Protection Act, if we receive actual knowledge that anyone under the age of 13 has provided personal information to us without the requisite and verifiable parental consent, we will delete that information from the Site as quickly as is reasonably practical.<span style=\"color: rgb(89, 89, 89); font-size: 11pt;\"><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></bdt></bdt></bdt></div></bdt></bdt></bdt></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><span style=\"font-size: 15px;\"><br></span></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"><span style=\"font-size: 15px;\"></span></bdt></bdt><div style=\"text-align: justify; line-height: 1.5;\"><bdt class=\"block-container if\" data-type=\"if\" id=\"87a7471d-cf82-1032-fdf8-601d37d7b017\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"privacy_policy_followup\" data-type=\"statement\" style=\"font-size: 14.6667px;\"><span style=\"font-size: 15px;\"></span></bdt></bdt></div><div class=\"MsoNormal\" style=\"line-height: 115%;\"><span style=\"font-size: 15px;\"><a name=\"_n081pott8yce\"></a></span></div><bdt class=\"block-component\"><bdt class=\"block-component\"></bdt><bdt class=\"block-container if\" data-type=\"if\"><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"><span style=\"font-size: 15px;\"></span></bdt></bdt></span></bdt></bdt><div class=\"MsoNormal\" style=\"line-height: 1;\"><span style=\"font-size: 15px;\"><a name=\"_sg28ikxq3swh\"></a></span><bdt class=\"block-component\"><bdt class=\"block-component\"></bdt></bdt> <bdt class=\"block-container if\" data-type=\"if\" id=\"95e88984-ac54-be9d-35de-f10fd010af14\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">COPYRIGHT INFRINGEMENTS</span></strong></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">We respect the\n  intellectual property rights of others. If you believe that any material available on or through the Site\n  infringes upon any copyright you own or control, please immediately notify us\n  using the contact information provided below (a “Notification”). A copy of your Notification will be sent to\n  the person who posted or stored the material addressed in the\n  Notification. Please be advised that\n  pursuant to applicable law you may be held liable for damages if you make material\n  misrepresentations in a Notification. Thus, if you are not sure that material\n  located on or linked to by the Site infringes your copyright, you should\n  consider first contacting an attorney.</span></div></bdt></bdt></bdt></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><a name=\"_k3mndam4w6w1\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">TERM AND\n  TERMINATION</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size: 15px; line-height: 115%; font-family: Arial; color: rgb(89, 89, 89);\">These Terms of Use shall remain in full force and effect while you use the Site. WITHOUT LIMITING ANY OTHER PROVISION OF THESE TERMS OF USE, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE SITE (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE TERMS OF USE OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE SITE OR DELETE <bdt class=\"block-container if\" data-type=\"if\" id=\"a6e121c2-36b4-5066-bf9f-a0a33512e768\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_account_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\">YOUR ACCOUNT AND </bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt>ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME,\n  WITHOUT WARNING, IN OUR SOLE DISCRETION.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">If we terminate\n  or suspend your account for any reason, you are prohibited from registering and\n  creating a new account under your name, a fake or borrowed name, or the name of\n  any third party, even if you may be acting on behalf of the third party. In\n  addition to terminating or suspending your account, we reserve the right to\n  take appropriate legal action, including without limitation pursuing civil,\n  criminal, and injunctive redress.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_e2dep1hfgltt\"></a><strong><span style=\"line-height: 115%; font-family: Arial;\"><span style=\"font-size: 19px;\">MODIFICATIONS AND INTERRUPTIONS</span></span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">We reserve the right to change, modify, or remove the contents of the Site at any time or for any reason at our sole discretion without notice. However, we have no obligation to update any information on our Site. We also reserve the right to modify or discontinue all or part of the Site without notice at any time. We will not be liable to you or any third party for any modification, price change, suspension, or discontinuance of the Site.  </span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">We cannot guarantee\n  the Site will be available at all times. We may experience hardware, software,\n  or other problems or need to perform maintenance related to the Site, resulting\n  in interruptions, delays, or errors. We\n  reserve the right to change, revise, update, suspend, discontinue, or otherwise\n  modify the Site at any time or for any reason without notice to you. You agree that we have no liability\n  whatsoever for any loss, damage, or inconvenience caused by your inability to\n  access or use the Site during any downtime or discontinuance of the Site. Nothing in these Terms of Use will be\n  construed to obligate us to maintain and support the Site or to supply any\n  corrections, updates, or releases in connection therewith.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_p6vbf8atcwhs\"></a><strong><span style=\"line-height: 115%; font-family: Arial;\"><span style=\"font-size: 19px;\">GOVERNING LAW</span></span></strong></div><div class=\"MsoNormal\" style=\"line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 115%;\"><bdt class=\"block-component\"><span style=\"font-size: 15px;\"></span></bdt></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">These Terms of Use and your use of the Site are governed by and construed in accordance with the laws of <bdt class=\"block-container if\" data-type=\"if\" id=\"b86653c1-52f0-c88c-a218-e300b912dd6b\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"governing_law\" data-type=\"statement\"></bdt><bdt data-type=\"body\">the State of <bdt class=\"block-container question question-in-editor\" data-id=\"b61250bd-6b61-32ea-a9e7-4a02690297c3\" data-type=\"question\">Texas</bdt></bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt> applicable to agreements made and to be entirely performed within<bdt class=\"block-container if\" data-type=\"if\" id=\"b86653c1-52f0-c88c-a218-e300b912dd6b\" style=\"font-size: 14.6667px;\"><bdt data-type=\"conditional-block\"> <span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\"><bdt class=\"block-container if\" data-type=\"if\" id=\"b86653c1-52f0-c88c-a218-e300b912dd6b\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"governing_law\" data-type=\"statement\"></bdt><bdt data-type=\"body\">the State of <bdt class=\"block-container question question-in-editor\" data-id=\"b61250bd-6b61-32ea-a9e7-4a02690297c3\" data-type=\"question\">Texas</bdt></bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt>, without regard to its conflict of law principles.<bdt class=\"block-component\"></bdt></span></span></span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_v5i5tjw62cyw\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">DISPUTE RESOLUTION</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 115%;\"><bdt class=\"block-container if\" data-type=\"if\" id=\"4de367b8-a92e-8bf8-bc2e-013cba6337f8\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"dispute_option\" data-type=\"statement\"></span></bdt> <bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_2\" style=\"line-height: 17.25px; text-align: left;\"><strong>Binding Arbitration</strong></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" style=\"text-align: left; line-height: 17.25px;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"text-align: left; line-height: 1.5;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">Any dispute <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">arising out of or in connection with this contract, including any question regarding its existence, validity or termination, shall be referred to and finally resolved by the International Commercial Arbitration Court under the European Arbitration Chamber (Belgium, Brussels, Avenue Louise, 146) according to the Rules of this ICAC, which, as a result of referring to it, is considered as the part of this clause. The number of arbitrators shall be <bdt class=\"question\">__________</bdt>. <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">The seat, or legal place, of arbitration shall be <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><bdt class=\"question\">__________</bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span>.<span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"> The language of the proceedings shall be <bdt class=\"question\">__________</bdt>.</span></span> The governing law of the contract shall be the substantive law of <span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt><bdt class=\"question\">__________</bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></span></span></span></span><bdt class=\"statement-end-if-in-editor\"></bdt></span></span>.<bdt class=\"statement-end-if-in-editor\"></bdt></span></span></span></div></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_2\" style=\"line-height: 17.25px; text-align: left;\"><a name=\"_inlv5c77dhih\"></a><strong>Restrictions</strong></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"text-align: left; line-height: 1.5;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">The Parties agree that any arbitration shall be limited to the Dispute between the Parties individually. To the full extent permitted by law, (a) no arbitration shall be joined with any other proceeding; (b) there is no right or authority for any Dispute to be arbitrated on a class-action basis or to utilize class action procedures; and (c) there is no right or authority for any Dispute to be brought in a purported representative capacity on behalf of the general public or any other persons.</span></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"heading_2\" style=\"line-height: 17.25px; text-align: left;\"><a name=\"_mdjlt1af25uq\"></a><strong>Exceptions to Arbitration</strong></div></bdt></bdt></bdt></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><br></div><div class=\"MsoNormal\" style=\"text-align: justify; line-height: 1;\"><bdt class=\"block-container if\" data-type=\"if\"><bdt data-type=\"conditional-block\"><bdt data-type=\"body\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"text-align: left; line-height: 1.5;\"><span style=\"font-size: 11pt; line-height: 16.8667px; color: rgb(89, 89, 89);\">The Parties agree that the following Disputes are not subject to the above provisions concerning binding arbitration: (a) any Disputes seeking to enforce or protect, or concerning the validity of, any of the intellectual property rights of a Party; (b) any Dispute related to, or arising from, allegations of theft, piracy, invasion of privacy, or unauthorized use; and (c) any claim for injunctive relief. If this provision is found to be illegal or unenforceable, then neither Party will elect to arbitrate any Dispute falling within that portion of this provision found to be illegal or unenforceable and such Dispute shall be decided by a court of competent jurisdiction within the courts listed for jurisdiction above, and the Parties agree to submit to the personal jurisdiction of that court.</span></div></bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"><span style=\"font-size: 15px;\"></span></bdt></bdt></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_mjgzo07ttzx5\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">CORRECTIONS</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">There may be\n  information on the Site that contains typographical errors, inaccuracies, or\n  omissions, including descriptions, pricing, availability, and various other\n  information. We reserve the right to\n  correct any errors, inaccuracies, or omissions and to change or update the\n  information on the Site at any time, without prior notice.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_gvi74blrahf9\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">DISCLAIMER</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">THE SITE IS PROVIDED\n  ON AN AS-IS AND AS-AVAILABLE BASIS. YOU\n  AGREE THAT YOUR USE OF THE SITE AND OUR SERVICES WILL BE AT YOUR SOLE RISK. TO THE\n  FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR\n  IMPLIED, IN CONNECTION WITH THE SITE AND YOUR USE THEREOF, INCLUDING, WITHOUT\n  LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n  PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT\n  THE ACCURACY OR COMPLETENESS OF THE SITE’S CONTENT OR THE CONTENT OF ANY\n  WEBSITES LINKED TO THE SITE AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY\n  FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2)\n  PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM\n  YOUR ACCESS TO AND USE OF THE SITE, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF\n  OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL\n  INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION\n  TO OR FROM THE SITE, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH\n  MAY BE TRANSMITTED TO OR THROUGH THE SITE BY ANY THIRD PARTY, AND/OR (6) ANY\n  ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF\n  ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR\n  OTHERWISE MADE AVAILABLE VIA THE SITE. WE DO NOT WARRANT, ENDORSE, GUARANTEE,\n  OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A\n  THIRD PARTY THROUGH THE SITE, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE\n  APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A\n  PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU\n  AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES. AS WITH THE\n  PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU\n  SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_4pjah3d0455q\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">LIMITATIONS OF LIABILITY</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">IN NO EVENT WILL WE OR OUR DIRECTORS, EMPLOYEES, OR AGENTS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, SPECIAL, OR PUNITIVE DAMAGES, INCLUDING LOST PROFIT, LOST REVENUE, LOSS OF DATA, OR OTHER DAMAGES ARISING FROM YOUR USE OF THE SITE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. <bdt class=\"block-container if\" data-type=\"if\" id=\"3c3071ce-c603-4812-b8ca-ac40b91b9943\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"limitations_liability_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\">NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO YOU FOR ANY CAUSE WHATSOEVER AND REGARDLESS OF THE FORM OF THE ACTION, WILL AT ALL TIMES BE LIMITED TO <bdt class=\"block-container if\" data-type=\"if\" id=\"73189d93-ed3a-d597-3efc-15956fa8e04e\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"limitations_liability_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\">THE LESSER OF THE AMOUNT PAID, IF ANY, BY YOU TO US<bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"limilation_liability_time_option\" data-type=\"statement\"></bdt> OR <bdt class=\"block-container question question-in-editor\" data-id=\"243ad246-9e92-b24d-beee-940be6aa7854\" data-type=\"question\">__________</bdt></bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt>. CERTAIN US STATE LAWS AND INTERNATIONAL LAWS DO NOT ALLOW LIMITATIONS ON IMPLIED WARRANTIES OR THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES. IF THESE LAWS APPLY TO YOU, SOME OR ALL OF THE ABOVE DISCLAIMERS OR LIMITATIONS MAY NOT APPLY TO YOU, AND YOU MAY HAVE ADDITIONAL RIGHTS.</bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt></span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_k5ap68aj1dd4\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">INDEMNIFICATION</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">You agree to\n  defend, indemnify, and hold us harmless, including our subsidiaries,\n  affiliates, and all of our respective officers, agents, partners, and\n  employees, from and against any loss, damage, liability, claim, or demand, including\n  reasonable attorneys’ fees and expenses, made by any third party due to or\n  arising out of: <bdt class=\"block-container if\" data-type=\"if\" id=\"475fffa5-05ca-def8-ac88-f426b238903c\"><bdt data-type=\"conditional-block\"><bdt class=\"block-component\" data-record-question-key=\"user_post_content_option\" data-type=\"statement\"></bdt><bdt data-type=\"body\">(1) your Contributions; </bdt></bdt><bdt class=\"statement-end-if-in-editor\" data-type=\"close\"></bdt></bdt>(<span style=\"font-size: 14.6667px;\">2</span>) use of the Site; (<span style=\"font-size: 14.6667px;\">3</span>) breach of these Terms of Use; (<span style=\"font-size: 14.6667px;\">4</span>) any breach of your representations and warranties set forth in these Terms of Use; (<span style=\"font-size: 14.6667px;\">5</span>) your violation of the rights of a third party, including but not limited to intellectual property rights; or (<span style=\"font-size: 14.6667px;\">6</span>) any overt harmful act toward any other user of the Site with whom you connected via the Site. Notwithstanding the foregoing, we reserve the right, at your expense, to assume the exclusive defense and control of any matter for which you are required to indemnify us, and you agree to cooperate, at your expense, with our defense of such claims. We will use reasonable efforts to notify you of any such claim, action, or proceeding which is subject to this indemnification upon becoming aware of it.</span><span style=\"color: rgb(89, 89, 89); font-size: 14.6667px;\"></span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_ftgg17oha0ep\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">USER DATA</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">We will maintain\n  certain data that you transmit to the Site for the purpose of managing the\n  performance of the Site, as well as data relating to your use of the Site. Although we perform regular routine backups\n  of data, you are solely responsible for all data that you transmit or that\n  relates to any activity you have undertaken using the Site. You agree\n  that we shall have no liability to you for any loss or corruption of any such\n  data, and you hereby waive any right of action against us arising from any such\n  loss or corruption of such data.</span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_dkovrslqodui\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">ELECTRONIC COMMUNICATIONS, TRANSACTIONS, AND SIGNATURES</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">Visiting the Site, sending us emails, and completing online forms constitute electronic communications. You consent to receive electronic communications, and you agree that all agreements, notices, disclosures, and other communications we provide to you electronically, via email and on the Site, satisfy any legal requirement that such communication be in writing. YOU HEREBY AGREE TO THE USE OF ELECTRONIC SIGNATURES, CONTRACTS, ORDERS, AND OTHER RECORDS, AND TO ELECTRONIC DELIVERY OF NOTICES, POLICIES, AND RECORDS OF TRANSACTIONS INITIATED OR COMPLETED BY US OR VIA THE SITE. You hereby waive any rights or requirements under any statutes, regulations, rules, ordinances, or other laws in any jurisdiction which require an original signature or delivery or retention of non-electronic records, or to payments or the granting of credits by any means other than electronic means. </span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\"><bdt class=\"block-component\"></bdt></span></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><a name=\"_cem9cu2usl7k\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">CALIFORNIA USERS\n  AND RESIDENTS</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">If any complaint\n  with us is not satisfactorily resolved, you can contact the Complaint\n  Assistance Unit of the Division of Consumer Services of the California\n  Department of Consumer Affairs in writing at 1625 North Market Blvd., Suite N\n  112, Sacramento, California 95834 or by telephone at (800) 952-5210 or (916)\n  445-1254.</span></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\"><bdt class=\"statement-end-if-in-editor\"></bdt></span></span></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 115%;\"><a name=\"_d4jvmcnxg0wt\"></a><strong><span style=\"line-height: 115%; font-family: Arial; font-size: 19px;\">MISCELLANEOUS</span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">These Terms of Use and any policies or operating rules posted by us on the Site or in respect to the Site constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Terms of Use shall not operate as a waiver of such right or provision. These Terms of Use operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time. We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control. If any provision or part of a provision of these Terms of Use is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Terms of Use and does not affect the validity and enforceability of any remaining provisions. There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Terms of Use or use of the Site. You agree that these Terms of Use will not be construed against us by virtue of having drafted them. You hereby waive any and all defenses you may have based on the electronic form of these Terms of Use and the lack of signing by the parties hereto to execute these Terms of Use.<bdt class=\"block-component\"></bdt></span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1.5;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" style=\"line-height: 1.5;\"><br></div><div class=\"MsoNormal\" data-custom-class=\"heading_1\" style=\"line-height: 1.5;\"><a name=\"_t4pq5cwn486q\"></a><strong><span style=\"line-height: 115%; font-family: Arial;\"><span style=\"font-size: 19px;\">CONTACT US</span> </span></strong></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size:11.0pt;line-height:115%;font-family:Arial;\n  Calibri;color:#595959;mso-themecolor:text1;mso-themetint:166;\">In order to resolve a complaint regarding the Site or to receive further information regarding use of the Site, please contact us at: </span></div></div><div align=\"center\" style=\"text-align: left; line-height: 1;\"><br></div><div align=\"center\" style=\"text-align: left;\"><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size: 15px; line-height: 115%; font-family: Arial; color: rgb(89, 89, 89);\"><bdt class=\"block-container question question-in-editor\" data-id=\"8a6919c4-2010-e7d6-2305-d74dfb08909d\" data-type=\"question\"><strong>DogeHouse</strong></bdt></span><span style=\"color: rgb(89, 89, 89);\"><strong><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></strong></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89);\"><strong><span style=\"font-size: 15px;\"><bdt class=\"question\">6705 West Highway 290</bdt><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"block-component\"></bdt></span></strong></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89);\"><strong><span style=\"font-size: 15px;\"><bdt class=\"question\">Austin</bdt><bdt class=\"block-component\"></bdt></span></strong><strong><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt>, <bdt class=\"question\">TX</bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></strong><strong><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></strong><strong><span style=\"font-size: 15px;\"><bdt class=\"block-component\"></bdt></span></strong></span> <span style=\"color: rgb(89, 89, 89);\"><strong><span style=\"font-size: 15px;\"><bdt class=\"question\">78735</bdt><bdt class=\"statement-end-if-in-editor\"></bdt> <bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt><bdt class=\"block-component\"></bdt></span></strong></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89);\"><strong><span style=\"font-size: 15px;\"><bdt class=\"question\">United States</bdt><bdt class=\"statement-end-if-in-editor\"></bdt><bdt class=\"else-block\"></bdt><bdt class=\"statement-end-if-in-editor\"></bdt></span></strong></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"color: rgb(89, 89, 89);\"><strong><span style=\"font-size: 15px;\">Phone: </span></strong><strong><span style=\"font-size: 15px;\"><span style=\"line-height: 115%; font-family: Arial;\"><bdt class=\"block-container question question-in-editor\" data-id=\"dd6f266f-438b-bfdc-9204-0b17e109e270\" data-type=\"question\">__________</bdt></span></span></strong></span><span style=\"font-size: 15px;\"><strong><span style=\"color: rgb(89, 89, 89);\"><bdt class=\"block-component\"></bdt></span></strong></span></div><div class=\"MsoNormal\" data-custom-class=\"body_text\" style=\"line-height: 1.5;\"><span style=\"font-size: 15px; line-height: 115%; font-family: Arial; color: rgb(89, 89, 89);\"><bdt class=\"block-container question question-in-editor\" data-id=\"fdc2b5b8-c95f-244b-23a7-287f82541348\" data-type=\"question\"><strong>benawadapps@gmail.com</strong></bdt></span></div><br></div><style>\n        ul {\n          list-style-type: square;\n        }\n        ul > li > ul {\n          list-style-type: circle;\n        }\n        ul > li > ul > li > ul {\n          list-style-type: square;\n        }\n        ol li {\n          font-family: Arial ;\n        }\n      </style>\n        </div>\n"
  },
  {
    "path": "kibbeh/scripts/syncTranslations.ts",
    "content": "// @ts-ignore\nimport config from \"../.prettierrc.js\";\nimport english from \"../public/locales/en/translation.json\";\nimport * as fs from \"fs\";\nimport { join } from \"path\";\nimport prettier from \"prettier\";\nimport { traverseTranslations } from \"./traverseTranslations\";\nimport { get, set } from \"lodash\";\n\nconst paths = traverseTranslations();\n\nfs.readdirSync(join(__dirname, \"../public/locales\")).forEach((locale) => {\n  if (locale === \"en\") {\n    return;\n  }\n  const filename = join(\n    __dirname,\n    \"../public/locales\",\n    locale,\n    \"translation.json\"\n  );\n  let data: any;\n  try {\n    data = JSON.parse(fs.readFileSync(filename, { encoding: \"utf-8\" }));\n  } catch (err) {\n    throw new Error(`${locale}: ${err.message}`);\n  }\n  paths.forEach((p) => {\n    if (get(data, p, null) === null) {\n      set(data, p, get(english, p));\n    }\n  });\n\n  fs.writeFileSync(\n    filename,\n    prettier.format(JSON.stringify(data), {\n      parser: \"json\",\n      ...config,\n    })\n  );\n});\n"
  },
  {
    "path": "kibbeh/scripts/traverseTranslations.ts",
    "content": "import translations from \"../public/locales/en/translation.json\";\n\nconst keys: string[] = [];\n\ntype TranslationRecord = {\n\t[P in string]: string | TranslationRecord;\n};\n\nconst _traverseTranslations = (obj: TranslationRecord, path: string[]) => {\n\tObject.keys(obj).forEach((key) => {\n\t\tif (key.startsWith(\"_\")) {\n\t\t\treturn;\n\t\t}\n\t\tconst objOrString = obj[key];\n\t\tif (typeof objOrString === \"string\") {\n\t\t\tkeys.push([...path, key].join(\".\"));\n\t\t} else {\n\t\t\t_traverseTranslations(objOrString, [...path, key]);\n\t\t}\n\t});\n};\n\nexport const traverseTranslations = () => {\n\t_traverseTranslations(translations, []);\n\treturn keys;\n};\n"
  },
  {
    "path": "kibbeh/scripts/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es6\",\n\t\t\"module\": \"commonjs\",\n\t\t\"lib\": [\"dom\", \"es6\", \"es2017\", \"esnext.asynciterable\"],\n\t\t\"sourceMap\": true,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\",\n\t\t\"removeComments\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"strictFunctionTypes\": true,\n\t\t\"noImplicitThis\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"emitDecoratorMetadata\": true,\n\t\t\"experimentalDecorators\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"baseUrl\": \".\"\n\t},\n\t\"exclude\": [\"node_modules\"],\n\t\"include\": [\"./*.ts\"]\n}\n"
  },
  {
    "path": "kibbeh/src/form-fields/FieldSpacer.tsx",
    "content": "import React from \"react\";\n\ninterface FieldSpacerProps {}\n\nexport const FieldSpacer: React.FC<FieldSpacerProps> = ({}) => {\n  return <div className={`flex my-6`} />;\n};\n"
  },
  {
    "path": "kibbeh/src/form-fields/InputField.tsx",
    "content": "import { useField } from \"formik\";\nimport React from \"react\";\nimport { Input } from \"../ui/Input\";\nimport { InputErrorMsg } from \"../ui/InputErrorMsg\";\n\nexport const InputField: React.FC<\n  React.DetailedHTMLProps<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    HTMLInputElement\n  > & {\n    name: string;\n    errorMsg?: string;\n    label?: string;\n    textarea?: boolean;\n    altErrorMsg?: string;\n    rows?: number;\n  }\n> = ({ label, textarea, errorMsg, ref: _, className, ...props }) => {\n  const [field, meta] = useField(props);\n\n  return (\n    <div className={`h-full w-full block ${className}`}>\n      {label ? (\n        <div className={`flex mb-2 text-primary-300`}>{label}</div>\n      ) : null}\n      <Input error={meta.error} textarea={textarea} {...field} {...props} />\n      {meta.error && meta.touched ? (\n        <div className={`flex mt-1`}>\n          <InputErrorMsg>{errorMsg || meta.error}</InputErrorMsg>\n        </div>\n      ) : null}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/global-stores/useAccountOverlay.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAccountOverlay = create(\n  combine(\n    {\n      isOpen: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useAskForMicStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAskForMicStore = create(\n  combine(\n    {\n      hasAsked: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useAudioTracks.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAudioTracks = create(\n  combine(\n    {\n      tracks: [] as MediaStreamTrack[],\n    },\n    (set) => ({\n      add: (track: MediaStreamTrack) =>\n        set((s) => ({ tracks: [...s.tracks, track] })),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useCurrentRoomIdStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useRoomChatStore } from \"../modules/room/chat/useRoomChatStore\";\n\ntype Fn = (currentRoomId: string | null) => string | null;\n\nexport const useCurrentRoomIdStore = create(\n  combine(\n    {\n      currentRoomId: null as string | null,\n    },\n    (set, get) => ({\n      set,\n      setCurrentRoomId: (currentRoomIdOrFn: string | null | Fn) => {\n        const id = get().currentRoomId;\n        const newId =\n          typeof currentRoomIdOrFn === \"function\"\n            ? currentRoomIdOrFn(id)\n            : currentRoomIdOrFn;\n        if (newId !== id) {\n          useRoomChatStore.getState().reset();\n        }\n        set({ currentRoomId: newId });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useDeafStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../modules/sound-effects/useSoundEffectStore\";\n\nexport const useDeafStore = create(\n  combine(\n    {\n      deafened: false,\n    },\n    (set) => ({\n      // don't call this directly unless you know what you are doing\n      // use useSetDeaf hook intead\n      setInternalDeaf: (deafened: boolean, playSound = true) => {\n        // to prevent sound overlapping\n        if (playSound) {\n          useSoundEffectStore\n            .getState()\n            .playSoundEffect(deafened ? \"deafen\" : \"undeafen\");\n        }\n        set({ deafened });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useDebugAudio.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nconst key = \"@debug-audio\";\n\nconst getDefaultValue = () => {\n  try {\n    return localStorage.getItem(key) === \"true\";\n  } catch {\n    return false;\n  }\n};\n\nexport const useDebugAudioStore = create(\n  combine(\n    {\n      debugAudio: getDefaultValue(),\n    },\n    (set) => ({\n      setDebugAudio: (v: boolean) => {\n        try {\n          localStorage.setItem(\"debug\", v ? \"*\" : \"\");\n          localStorage.setItem(key, \"\" + v);\n        } catch {}\n        set({ debugAudio: v });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useDownloadAlertStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nconst downloadalertkey = \"@baklava/showDownloadAlert\";\n\nconst getDefaultValues = () => {\n  try {\n    const v = JSON.parse(localStorage.getItem(downloadalertkey) || \"true\");\n    return {\n      shouldAlert: v,\n    };\n  } catch {\n    return {\n      shouldAlert: true,\n    };\n  }\n};\n\nexport const useDownloadAlertStore = create(\n  combine(getDefaultValues(), (set) => ({\n    setData: (x: { shouldAlert: boolean }) => {\n      try {\n        localStorage.setItem(downloadalertkey, JSON.stringify(x.shouldAlert));\n      } catch {}\n\n      set(x);\n    },\n  }))\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useElectronMobileStore.ts",
    "content": "import isElectron from \"is-electron\";\nimport { useScreenType } from \"../shared-hooks/useScreenType\";\nimport { useHostStore } from \"./useHostStore\";\n\nexport const useIsElectronMobile = () => {\n    const screenType = useScreenType();\n    return (\n        screenType === \"fullscreen\" &&\n        isElectron() &&\n        !useHostStore.getState().isLinux\n    );\n};\n"
  },
  {
    "path": "kibbeh/src/global-stores/useEmojiPickerStore.ts",
    "content": "import { CustomEmote } from \"./../modules/room/chat/EmoteData\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useEmojiPickerStore = create(\n  combine(\n    {\n      open: false,\n      query: \"\",\n      queryMatches: [] as CustomEmote[],\n      keyboardHoveredEmoji: null as null | string,\n    },\n    (set) => ({\n      setOpen: (open: boolean) => set({ open }),\n      setQuery: (query: string) => set({ query }),\n      setQueryMatches: (queryMatches: CustomEmote[]) => set({ queryMatches }),\n      setKeyboardHoveredEmoji: (keyboardHoveredEmoji: string | null) =>\n        set({ keyboardHoveredEmoji }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useGlobalVolumeStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useGlobalVolumeStore = create(\n  combine(\n    {\n      volume: 100,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useHostStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nconst hostPlatformkey = \"@app/hostPlatform\";\n\nconst getDefaultValues = () => {\n  try {\n    const v = JSON.parse(\n      localStorage.getItem(hostPlatformkey) ||\n        '{\"isLinux\":false,\"isWin\":false,\"isMac\":false}'\n    );\n    return { isLinux: v.isLinux, isWin: v.isWin, isMac: v.isMac };\n  } catch {\n    return { isLinux: false, isWin: false, isMac: false };\n  }\n};\n\nexport const useHostStore = create(\n  combine(getDefaultValues(), (set) => ({\n    setData: (x: { isLinux: boolean; isWin: boolean; isMac: boolean }) => {\n      try {\n        localStorage.setItem(hostPlatformkey, JSON.stringify(x));\n      } catch {}\n\n      set(x);\n    },\n  }))\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useKeyMapStore.ts",
    "content": "import { KeyMap } from \"react-hotkeys\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport isElectron from \"is-electron\";\n\nlet ipcRenderer: any = undefined;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\n\nexport const REQUEST_TO_SPEAK_KEY = \"@keybind/invite\";\nexport const INVITE_KEY = \"@keybind/invite\";\nexport const MUTE_KEY = \"@keybind/mute\";\nexport const DEAF_KEY = \"@keybind/deafen\";\nexport const CHAT_KEY = \"@keybind/chat\";\nexport const PTT_KEY = \"@keybind/ptt\";\nexport const OVERLAY_KEY = \"@keybind/overlay\";\n\nfunction getKeybind(actionKey: string, defaultKeybind: string) {\n  let v = \"\";\n  try {\n    v = localStorage.getItem(actionKey) || \"\";\n  } catch { }\n  if (isElectron()) {\n    ipcRenderer.send(actionKey, v || defaultKeybind);\n  }\n  return v || defaultKeybind;\n}\n\nfunction getRequestToSpeakKeybind() {\n  return getKeybind(REQUEST_TO_SPEAK_KEY, \"Control+8\");\n}\n\nfunction getInviteKeybind() {\n  return getKeybind(INVITE_KEY, \"Control+7\");\n}\n\nfunction getMuteKeybind() {\n  return getKeybind(MUTE_KEY, \"Control+m\");\n}\n\nfunction getDeafKeybind() {\n  return getKeybind(DEAF_KEY, \"Control+1\");\n}\n\nfunction getChatKeybind() {\n  return getKeybind(CHAT_KEY, \"Control+9\");\n}\n\nfunction getPTTKeybind() {\n  return getKeybind(PTT_KEY, \"Control+0\");\n}\n\nfunction getOverlayKeybind() {\n  return getKeybind(OVERLAY_KEY, \"Control+2\");\n}\n\nconst keyMap: KeyMap = {\n  REQUEST_TO_SPEAK: getRequestToSpeakKeybind(),\n  INVITE: getInviteKeybind(),\n  MUTE: getMuteKeybind(),\n  DEAF: getDeafKeybind(),\n  CHAT: getChatKeybind(),\n  OVERLAY: getOverlayKeybind(),\n  PTT: [\n    { sequence: getPTTKeybind(), action: \"keydown\" },\n    { sequence: getPTTKeybind(), action: \"keyup\" },\n  ],\n};\n\nconst keyNames: KeyMap = {\n  REQUEST_TO_SPEAK: getRequestToSpeakKeybind(),\n  INVITE: getInviteKeybind(),\n  MUTE: getMuteKeybind(),\n  DEAF: getDeafKeybind(),\n  CHAT: getChatKeybind(),\n  PTT: getPTTKeybind(),\n  OVERLAY: getOverlayKeybind(),\n};\n\nexport const useKeyMapStore = create(\n  combine(\n    {\n      keyMap,\n      keyNames,\n    },\n    (set) => ({\n      setRequestToSpeakKeybind: (id: string) => {\n        try {\n          localStorage.setItem(REQUEST_TO_SPEAK_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(REQUEST_TO_SPEAK_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: { ...x.keyMap, REQUEST_TO_SPEAK: id },\n          keyNames: { ...x.keyNames, REQUEST_TO_SPEAK: id },\n        }));\n      },\n      setInviteKeybind: (id: string) => {\n        try {\n          localStorage.setItem(INVITE_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(INVITE_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: { ...x.keyMap, INVITE: id },\n          keyNames: { ...x.keyNames, INVITE: id },\n        }));\n      },\n      setMuteKeybind: (id: string) => {\n        try {\n          localStorage.setItem(MUTE_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(MUTE_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: { ...x.keyMap, MUTE: id },\n          keyNames: { ...x.keyNames, MUTE: id },\n        }));\n      },\n      setDeafKeybind: (id: string) => {\n        try {\n          localStorage.setItem(DEAF_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(DEAF_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: { ...x.keyMap, DEAF: id },\n          keyNames: { ...x.keyNames, DEAF: id },\n        }));\n      },\n      setChatKeybind: (id: string) => {\n        try {\n          localStorage.setItem(CHAT_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(CHAT_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: { ...x.keyMap, CHAT: id },\n          keyNames: { ...x.keyNames, CHAT: id },\n        }));\n      },\n      setOverlayKeybind: (id: string) => {\n        try {\n          localStorage.setItem(OVERLAY_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(OVERLAY_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: { ...x.keyMap, OVERLAY: id },\n          keyNames: { ...x.keyNames, OVERLAY: id },\n        }));\n      },\n      setPTTKeybind: (id: string) => {\n        try {\n          localStorage.setItem(PTT_KEY, id);\n          if (isElectron()) {\n            ipcRenderer.send(PTT_KEY, id);\n          }\n        } catch { }\n        set((x) => ({\n          keyMap: {\n            ...x.keyMap,\n            PTT: [\n              { sequence: id, action: \"keydown\" },\n              { sequence: id, action: \"keyup\" },\n            ],\n          },\n          keyNames: { ...x.keyNames, PTT: id },\n        }));\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useMicPermErrorStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useMicPermErrorStore = create(\n  combine(\n    {\n      error: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useMuteStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../modules/sound-effects/useSoundEffectStore\";\nimport { useWakeLockStore } from \"../shared-hooks/useScreenWakeLockStore\";\n\nexport const useMuteStore = create(\n  combine(\n    {\n      muted: false,\n    },\n    (set) => ({\n      // don't call this directly unless you know what you are doing\n      // use useSetMute hook intead\n      setInternalMute: (muted: boolean, playSound = true) => {\n        if (muted) {\n          useWakeLockStore.getState().releaseWakeLock();\n        } else {\n          useWakeLockStore.getState().requestWakeLock();\n        }\n        // to prevent sound overlapping\n        if (playSound) {\n          useSoundEffectStore\n            .getState()\n            .playSoundEffect(muted ? \"mute\" : \"unmute\");\n        }\n        set({ muted });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useOverlayStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport isElectron from \"is-electron\";\n\nlet ipcRenderer: any = undefined;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\n\nconst overlayAppTitleKey = \"@overlay/app_title\";\n\nconst getDefaultValues = () => {\n  try {\n    const v = localStorage.getItem(overlayAppTitleKey);\n    if (isElectron()) {\n      ipcRenderer.send(overlayAppTitleKey, v || \"\");\n    }\n    return {\n      appTitle: v || \"\",\n    };\n  } catch {\n    return {\n      appTitle: \"\",\n    };\n  }\n};\n\nexport const useOverlayStore = create(\n  combine(getDefaultValues(), (set) => ({\n    setData: (x: { appTitle: string }) => {\n      try {\n        localStorage.setItem(overlayAppTitleKey, x.appTitle);\n        if (isElectron()) {\n          ipcRenderer.send(overlayAppTitleKey, x.appTitle);\n        }\n      } catch {}\n\n      set(x);\n    },\n  }))\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useProducerStore.ts",
    "content": "import { Producer } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useProducerStore = create(\n  combine(\n    {\n      producer: null as Producer | null,\n    },\n    (set) => ({\n      add: (p: Producer) =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n\n          return { producer: p };\n        }),\n      close: () =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n\n          return {\n            producer: null,\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useRoomChatMentionStore.ts",
    "content": "import { BaseUser } from \"@dogehouse/kebab\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../modules/sound-effects/useSoundEffectStore\";\n\nexport const useRoomChatMentionStore = create(\n  combine(\n    {\n      queriedUsernames: [] as BaseUser[],\n      activeUsername: \"\",\n      iAmMentioned: 0,\n    },\n    (set) => ({\n      setQueriedUsernames: (queriedUsernames: BaseUser[]) =>\n        set({\n          queriedUsernames,\n        }),\n      setActiveUsername: (activeUsername: string) => {\n        return set({\n          activeUsername,\n        });\n      },\n      resetIAmMentioned: () =>\n        set({\n          iAmMentioned: 0,\n        }),\n      incrementIAmMentioned: () => {\n        useSoundEffectStore.getState().playSoundEffect(\"roomChatMention\");\n        set((x) => ({ iAmMentioned: x.iAmMentioned + 1 }));\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useSocketStatus.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype State =\n  | \"auth-good\"\n  | \"open\"\n  | \"connecting\"\n  | \"closed-by-server\"\n  | \"closed\";\n\nexport const useSocketStatus = create(\n  combine(\n    {\n      status: \"connecting\" as State,\n    },\n    (set) => ({\n      setStatus: (status: State) => set({ status }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/global-stores/useStatus.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype State =\n  | \"init\"\n  | \"ws-disconnected\"\n  | \"voice-server-disconnected\"\n  | \"connected-no-room\"\n  | \"connected-listener\"\n  | \"bad-auth\"\n  | \"killed\"\n  | \"connected-speaker\";\n\nexport const useStatus = create(\n  combine(\n    {\n      status: \"init\" as State,\n    },\n    (set) => ({\n      setStatus: (status: State) => set({ status }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/globals.d.ts",
    "content": "import \"@testing-library/jest-dom/extend-expect\";\n"
  },
  {
    "path": "kibbeh/src/icons/BotIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction BotIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      fill=\"currentColor\"\n      viewBox=\"0 0 512 512\"\n      {...props}\n    >\n      <path\n        fill=\"currentColor\"\n        d=\"M256 32C132.8 32 32 132.8 32 256s100.8 224 224 224 224-100.8 224-224S379.2 32 256 32zm-82.16 280A56 56 0 11228 257.84 56 56 0 01173.84 312zm168 0A56 56 0 11396 257.84 56 56 0 01341.84 312z\"\n      />\n    </svg>\n  );\n}\n\nexport default BotIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/DeveloperIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction DeveloperIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      viewBox=\"0 0 21 21\"\n      width={16}\n      height={16}\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M18.5 10H17V6C17 4.9 16.1 4 15 4H11V2.5C11 1.1 9.9 0 8.5 0C7.1 0 6 1.1 6 2.5V4H2C0.9 4 0 4.9 0 6V9.8H1.5C3 9.8 4.2 11 4.2 12.5C4.2 14 3 15.2 1.5 15.2H0V19C0 20.1 0.9 21 2 21H5.8V19.5C5.8 18 7 16.8 8.5 16.8C10 16.8 11.2 18 11.2 19.5V21H15C16.1 21 17 20.1 17 19V15H18.5C19.9 15 21 13.9 21 12.5C21 11.1 19.9 10 18.5 10Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport default DeveloperIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/LgLogo.tsx",
    "content": "import * as React from \"react\";\n\nfunction LgLogo(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width=\"168\"\n      height=\"40\"\n      viewBox=\"0 0 168 40\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M56.0865 13.1801H60.3243C62.746 13.1801 64.3892 13.7069 65.6865 14.8483C67.2433 16.341 68.1081 18.4483 68.0216 20.6434C68.1081 22.9263 67.2433 25.1214 65.6 26.7019C64.1297 27.9311 62.3135 28.5457 60.4108 28.4579H56V13.1801H56.0865ZM59.1135 25.8238H59.7189C61.0162 25.9116 62.227 25.5604 63.2649 24.7702C64.3892 23.7165 64.9946 22.2239 64.9081 20.7312C64.9081 17.7459 63.0919 15.7264 60.2378 15.7264H59.1135V25.8238Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M80.9937 23.1896C80.9937 26.2627 78.4856 28.7212 75.4586 28.7212H75.3721C72.4315 28.8968 69.9234 26.5261 69.7505 23.5408C69.7505 23.3652 69.7505 23.2774 69.7505 23.1018C69.664 20.1165 71.9126 17.5701 74.8532 17.4823C75.0261 17.4823 75.1991 17.4823 75.3721 17.4823C78.3991 17.3945 80.9072 19.7652 80.9937 22.8384C80.9937 22.9262 80.9937 23.1018 80.9937 23.1896ZM72.518 23.1018C72.4315 24.6823 73.5559 25.9993 75.1126 26.0871C76.6694 26.1749 77.9667 25.0335 78.0532 23.453C78.0532 23.3652 78.0532 23.2774 78.0532 23.1018C78.0532 21.1701 77.0153 20.0287 75.2856 20.0287C73.7288 20.0287 72.518 21.1701 72.4315 22.7506C72.518 22.9262 72.518 23.014 72.518 23.1018Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M91.2873 17.8339H94.0549V27.5801C94.0549 29.7752 93.6225 31.0045 92.6711 31.9703C91.4603 33.024 89.9035 33.6386 88.3468 33.5508C86.4441 33.6386 84.6279 32.8484 83.2441 31.4435L84.9738 29.5996C85.7522 30.5655 86.963 31.0923 88.1738 31.0923C89.7306 31.2679 91.0279 30.1264 91.2008 28.546C91.2008 28.3704 91.2008 28.1948 91.2008 27.9313V26.8777C90.336 28.0191 88.9522 28.6338 87.5684 28.6338C84.7144 28.6338 82.6387 26.2631 82.6387 23.1021C82.6387 19.6778 84.7143 17.3949 87.8279 17.3949C89.1252 17.3071 90.4225 17.9217 91.2008 19.0631L91.2873 17.8339ZM85.7522 23.0143C85.5792 24.5948 86.79 25.9997 88.3468 26.0875C89.2117 26.1753 90.0765 25.824 90.6819 25.1216C91.1144 24.5948 91.2873 23.8924 91.2008 23.1899C91.2873 22.3119 91.0279 21.4339 90.4225 20.7314C89.9035 20.2046 89.2116 19.9412 88.5198 19.9412C86.963 19.9412 85.7522 21.1704 85.7522 22.6631C85.7522 22.8387 85.7522 22.9265 85.7522 23.0143Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M99.1564 23.8043C99.4158 25.297 100.8 26.3506 102.356 26.175C103.135 26.0872 103.913 25.5604 104.346 24.858L106.94 25.5604C105.989 27.5799 104.086 28.8091 101.924 28.8091C98.9834 28.9847 96.3888 26.614 96.3023 23.5409C96.3023 23.3653 96.3023 23.2775 96.3023 23.1019C96.1293 20.1165 98.378 17.658 101.319 17.4824C101.492 17.4824 101.578 17.4824 101.751 17.4824C104.692 17.3946 107.286 19.6775 107.373 22.7507C107.373 22.9263 107.373 23.1019 107.373 23.2775V23.8043H99.1564ZM104.346 22.136C104.173 20.819 103.048 19.8531 101.664 19.8531C100.367 19.7653 99.2429 20.7312 99.0699 22.136H104.346Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M112.563 28.3701H109.449V13.1801H112.563V19.5897H118.271V13.1801H121.384V28.3701H118.271V22.1361H112.563V28.3701Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M134.443 23.1896C134.443 26.2627 131.935 28.7212 128.908 28.7212H128.821C125.881 28.8968 123.286 26.5261 123.2 23.5408C123.2 23.3652 123.2 23.2774 123.2 23.1018C123.113 20.1165 125.362 17.5701 128.302 17.4823C128.475 17.4823 128.648 17.4823 128.821 17.4823C131.848 17.3945 134.356 19.7652 134.443 22.8384C134.443 22.9262 134.443 23.1018 134.443 23.1896ZM126.054 23.1018C125.967 24.6823 127.092 25.9993 128.648 26.0871C130.205 26.1749 131.502 25.0335 131.589 23.453C131.589 23.3652 131.589 23.2774 131.589 23.1018C131.589 21.1701 130.551 20.0287 128.821 20.0287C127.265 20.0287 126.054 21.1701 125.967 22.7506C126.054 22.9262 126.054 23.014 126.054 23.1018Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M145.514 17.8335V24.5066C145.514 26.0871 145.168 26.9651 144.304 27.6675C143.352 28.4578 142.141 28.809 140.931 28.7212C139.633 28.809 138.423 28.37 137.471 27.5797C136.693 26.7895 136.26 25.648 136.347 24.5066V17.8335H139.201V24.331C139.114 25.2968 139.72 26.1749 140.671 26.2627C140.758 26.2627 140.844 26.2627 140.931 26.2627C141.796 26.3505 142.574 25.648 142.574 24.77C142.574 24.6822 142.574 24.5944 142.574 24.5066V17.8335H145.514Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M152.605 20.9067V20.7311C152.605 20.1165 152.086 19.6775 151.308 19.6775C150.53 19.6775 150.184 20.0287 150.184 20.6433C150.184 20.9067 150.27 21.1701 150.443 21.3458C150.789 21.6092 150.789 21.6092 152.259 22.0482C154.594 22.7506 155.546 23.7165 155.546 25.3847C155.546 27.4042 153.903 28.8091 151.481 28.8091C149.059 28.8091 147.503 27.492 147.416 25.3847H150.184C150.357 26.2628 150.702 26.7018 151.567 26.7018C152.173 26.7896 152.605 26.3506 152.692 25.736C152.692 25.736 152.692 25.736 152.692 25.6481C152.692 25.0335 152.259 24.6823 150.789 24.2433C148.54 23.5409 147.502 22.575 147.502 20.9067C147.502 18.8872 149.059 17.5702 151.481 17.5702C153.903 17.5702 155.286 18.7994 155.373 20.9945L152.605 20.9067Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M159.783 23.8043C160.043 25.2969 161.427 26.3506 162.983 26.175C163.762 26.0872 164.54 25.5604 164.973 24.8579L167.567 25.5604C166.616 27.5798 164.713 28.8091 162.551 28.8091C159.61 28.8969 157.016 26.5262 156.929 23.5409C156.929 23.4531 156.929 23.2775 156.929 23.1896C156.756 20.2043 159.005 17.7458 161.945 17.5702C162.118 17.5702 162.291 17.5702 162.378 17.5702C165.405 17.4824 167.913 19.7653 168 22.8384C168 23.014 168 23.1896 168 23.3653V23.8921H159.783V23.8043ZM164.973 22.136C164.8 20.8189 163.675 19.8531 162.291 19.8531C160.994 19.7653 159.87 20.7311 159.697 22.136H164.973Z\"\n        fill=\"#FD4D4D\"\n      />\n      <path\n        d=\"M10.0006 0H29.3993C34.9418 0 39.3999 4.52599 39.3999 10.1529V29.8471C39.3999 35.474 34.9418 40 29.3993 40H10.0006C4.45809 40 0 35.474 0 29.8471V10.1529C0 4.52599 4.45809 0 10.0006 0Z\"\n        fill=\"#EFE7DC\"\n      />\n      <path\n        d=\"M5.30238 22.1408C5.18189 21.2846 5.66385 20.4283 6.38678 20.0613C7.95314 19.0827 9.88096 18.9604 11.5678 19.6944C12.1703 20.0613 12.7727 20.306 13.3751 20.7953C13.9776 21.1622 14.2186 22.0185 13.8571 22.7525C13.3751 23.6087 12.8932 24.3427 12.1703 25.0766C10.8449 26.7892 8.4351 27.0338 6.62776 25.6882C6.50727 25.5659 6.26629 25.4436 6.14581 25.1989C5.54336 24.3427 5.18189 23.2418 5.30238 22.1408Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M25.6633 17.6147C24.9404 17.4924 24.3379 17.4924 23.7355 17.2477C23.133 17.0031 22.8921 16.3915 23.0125 15.6575C23.374 13.945 24.8199 12.7217 26.5067 12.3548C28.3141 11.9878 30.1214 12.9664 30.9648 14.6789C31.0853 14.9236 30.8443 15.4129 30.8443 15.7798H30.6033V14.8012L29.3985 16.2691C28.6755 17.0031 27.7116 17.3701 26.6272 17.2477C26.1453 17.2477 26.0248 16.8808 26.0248 16.3915C26.0248 15.7798 26.0248 15.2905 26.1453 14.6789C26.2657 14.1896 26.5067 13.578 26.7477 13.0887C26.0248 12.7217 25.0609 13.3334 24.3379 14.5566C23.856 15.5352 24.2174 16.3915 25.6633 17.6147Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M22.6523 31.315C20.6039 31.9266 18.5556 33.0275 16.2663 32.5383C15.9049 32.4159 15.5434 32.2936 15.1819 32.1713C13.4951 31.4373 11.6878 30.948 9.88043 30.8257C8.43456 30.5811 7.10919 30.0918 5.9043 29.3578C6.14527 29.2355 6.38625 29.2355 6.62723 29.2355C7.83212 28.9909 9.1575 29.2355 10.3624 29.7248C11.8083 30.4587 13.2541 30.8257 14.7 31.682C15.6639 32.049 16.7483 32.1713 17.7122 31.8043C18.7966 31.5597 19.7605 31.315 20.8449 31.0704C21.4474 31.0704 22.0498 31.0704 22.6523 31.0704V31.315Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M13.2528 10.8868C12.8914 9.78589 11.566 9.17427 10.4816 9.66356C10.3611 9.66356 10.3611 9.66356 10.2406 9.78589C9.27671 10.1529 8.79476 11.0091 8.67427 11.9877C8.67427 12.11 8.67427 12.3547 8.55378 12.477C8.3128 13.5779 8.67427 13.9449 9.75867 14.0672C10.1201 14.0672 10.4816 14.0672 10.8431 14.0672C11.9275 14.0672 12.8914 13.3333 13.2528 12.2324C13.3733 11.8654 13.3733 11.3761 13.2528 10.8868ZM10.3611 13.8226C9.63818 13.7003 9.15622 13.0886 9.15622 12.477C9.27671 11.2538 10.1201 10.2752 11.325 10.1529C9.3972 11.0091 9.63818 12.3547 10.3611 13.8226Z\"\n        fill=\"black\"\n      />\n    </svg>\n  );\n}\n\nexport default LgLogo;\n"
  },
  {
    "path": "kibbeh/src/icons/Link.tsx",
    "content": "import * as React from \"react\";\n\nfunction LinkIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"8\"\n      viewBox=\"0 0 16 8\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M1.52143 4C1.52143 2.63214 2.63214 1.52143 4 1.52143H7.14286V0H4C1.79286 0 0 1.79286 0 4C0 6.20714 1.79286 8 4 8H7.14286V6.47857H4C2.63214 6.47857 1.52143 5.36786 1.52143 4ZM4.57143 4.78571H11.4286V3.21429H4.57143V4.78571ZM12 0H8.85714V1.52143H12C13.3679 1.52143 14.4786 2.63214 14.4786 4C14.4786 5.36786 13.3679 6.47857 12 6.47857H8.85714V8H12C14.2071 8 16 6.20714 16 4C16 1.79286 14.2071 0 12 0Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport default LinkIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/LogoIcon.tsx",
    "content": "import * as React from \"react\";\n\ninterface SvgDogehouseIconProps extends React.SVGProps<SVGSVGElement> {\n  fillCurrent?: boolean;\n}\n\nfunction SvgDogehouseIcon(props: SvgDogehouseIconProps) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g>\n        <path d=\"M4.2,5.5C3.9,4.9,3.8,4.4,4.6,4.1C4.1,4.1,3.8,4.5,3.7,5C3.7,5.2,3.9,5.5,4.2,5.5z\" />\n        <path\n          className={props.fillCurrent ? \"fill-current\" : \"\"}\n          d=\"M11.9,0H4.1C1.8,0,0,1.8,0,4.1v7.9C0,14.2,1.8,16,4.1,16h7.9c2.3,0,4.1-1.8,4.1-4.1V4.1C16,1.8,14.2,0,11.9,0z\n        M3.5,5c0,0,0-0.1,0-0.2c0-0.4,0.2-0.7,0.6-0.9c0,0,0,0,0.1,0c0.4-0.2,1,0,1.1,0.5c0,0.2,0,0.4,0,0.5c-0.1,0.4-0.5,0.7-1,0.7\n        c-0.1,0-0.3,0-0.4,0C3.5,5.6,3.4,5.4,3.5,5z M2.2,8.9c0-0.3,0.1-0.7,0.4-0.8C3.2,7.6,4,7.6,4.7,7.9C4.9,8,5.2,8.1,5.4,8.3\n        c0.2,0.1,0.3,0.5,0.2,0.8C5.4,9.4,5.2,9.7,4.9,10c-0.5,0.7-1.5,0.8-2.3,0.2c0,0-0.1-0.1-0.2-0.2C2.3,9.7,2.1,9.3,2.2,8.9z\n        M9.2,12.5c-0.8,0.2-1.7,0.7-2.6,0.5c-0.1,0-0.3-0.1-0.4-0.1c-0.7-0.3-1.4-0.5-2.2-0.5c-0.6-0.1-1.1-0.3-1.6-0.6c0.1,0,0.2,0,0.3,0\n        c0.5-0.1,1,0,1.5,0.2c0.6,0.3,1.2,0.4,1.8,0.8c0.4,0.1,0.8,0.2,1.2,0c0.4-0.1,0.8-0.2,1.3-0.3C8.7,12.4,9,12.4,9.2,12.5L9.2,12.5z\n        M12.5,6.3L12.5,6.3l-0.1-0.4l-0.5,0.6c-0.3,0.3-0.7,0.4-1.1,0.4c-0.2,0-0.2-0.1-0.2-0.3c0-0.2,0-0.4,0-0.7c0-0.2,0.1-0.4,0.2-0.6\n        c-0.3-0.1-0.7,0.1-1,0.6c-0.2,0.4,0,0.7,0.5,1.2c-0.3,0-0.5,0-0.8-0.1C9.4,6.8,9.3,6.6,9.3,6.3c0.1-0.7,0.7-1.2,1.4-1.3\n        c0.7-0.1,1.5,0.2,1.8,0.9C12.6,6,12.5,6.2,12.5,6.3z\"\n        />\n      </g>\n    </svg>\n  );\n}\n\nexport default SvgDogehouseIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/MacCloseIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction MacCloseIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...props}>\n      <path\n        stroke=\"currentColor\"\n        fill=\"none\"\n        d=\"M8.5,3.5 L6,6 L3.5,3.5 L6,6 L3.5,8.5 L6,6 L8.5,8.5 L6,6 L8.5,3.5 Z\"\n      ></path>\n    </svg>\n  );\n}\n\nexport default MacCloseIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/MacMaximizeIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction MacMaximizeIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...props}>\n      <g fill=\"currentColor\" fillRule=\"evenodd\">\n        <path\n          d=\"M5,3 C5,3 5,6.1325704 5,6.48601043 C5,6.83945045 5.18485201,7 5.49021559,7 L9,7 L9,6 L8,6 L8,5 L7,5 L7,4 L6,4 L6,3 L5,3 Z\"\n          transform=\"rotate(180 7 5)\"\n        ></path>\n        <path d=\"M3,5 C3,5 3,8.1325704 3,8.48601043 C3,8.83945045 3.18485201,9 3.49021559,9 L7,9 L7,8 L6,8 L6,7 L5,7 L5,6 L4,6 L4,5 L3,5 Z\"></path>\n      </g>\n    </svg>\n  );\n}\n\nexport default MacMaximizeIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/MacMinimizeIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction MacMinimizeIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...props}>\n      <rect\n        fill=\"currentColor\"\n        width=\"8\"\n        height=\"2\"\n        x=\"2\"\n        y=\"5\"\n        fillRule=\"evenodd\"\n      ></rect>\n    </svg>\n  );\n}\n\nexport default MacMinimizeIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/OutlineGlobe.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgOutlineGlobe(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M8 .004h-.026a7.957 7.957 0 00-5.637 2.35 7.96 7.96 0 00-2.333 5.65c0 2.135.83 4.139 2.337 5.65a7.95 7.95 0 005.636 2.35h.027c4.41 0 7.997-3.588 7.997-8 0-4.411-3.591-8-8-8zm6.928 7.462h-2.983a15.152 15.152 0 00-.377-2.93c.658-.212 1.296-.482 1.91-.809a6.892 6.892 0 011.45 3.739zm-7.466 0H5.086c.031-.942.146-1.835.339-2.658.669.15 1.349.243 2.037.273v2.385zm0 1.077v2.38c-.684.031-1.368.124-2.037.274a13.675 13.675 0 01-.339-2.654h2.376zm1.077 0h2.356c-.03.938-.146 1.83-.338 2.65a11.474 11.474 0 00-2.018-.27v-2.38zm0-1.077V5.08c.684-.03 1.36-.123 2.018-.269.192.823.308 1.712.338 2.654H8.54zm4.206-4.535c-.473.235-.962.435-1.461.596-.273-.823-.62-1.534-1.02-2.096.924.32 1.765.831 2.48 1.5zm-2.476.873c-.565.123-1.146.2-1.73.231V1.212c.653.354 1.291 1.304 1.73 2.592zM7.462 1.197V4.03a10.628 10.628 0 01-1.749-.234c.446-1.3 1.092-2.25 1.75-2.6zm-1.753.246c-.396.558-.738 1.265-1.011 2.08a10.918 10.918 0 01-1.442-.592A6.852 6.852 0 015.71 1.443zM2.522 3.73c.607.323 1.242.593 1.892.8-.22.92-.346 1.904-.377 2.931h-2.96c.1-1.361.6-2.646 1.445-3.73zm-1.45 4.812h2.96c.036 1.027.162 2.011.378 2.93-.65.212-1.284.481-1.892.8a6.893 6.893 0 01-1.445-3.73zm2.18 4.535c.466-.231.95-.431 1.446-.593.273.82.615 1.523 1.011 2.085a7.023 7.023 0 01-2.457-1.492zm2.461-.87a10.69 10.69 0 011.75-.234v2.838c-.662-.35-1.304-1.304-1.75-2.604zm2.826 2.589V11.97c.584.03 1.165.107 1.73.23-.439 1.293-1.077 2.243-1.73 2.597zm1.73-.22c.4-.561.745-1.273 1.018-2.096.5.162.992.366 1.461.6a7.038 7.038 0 01-2.48 1.497zm3.21-2.3a11.698 11.698 0 00-1.91-.807c.214-.92.341-1.9.376-2.927h2.983a6.863 6.863 0 01-1.45 3.735z\" />\n    </svg>\n  );\n}\n\nexport default SvgOutlineGlobe;\n"
  },
  {
    "path": "kibbeh/src/icons/Share.tsx",
    "content": "import * as React from \"react\";\n\nfunction ShareIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M12.8236 11.3369C12.2182 11.3369 11.6529 11.5378 11.2493 11.9398L5.47729 8.60324C5.51771 8.40218 5.55812 8.24124 5.55812 8.04026C5.55812 7.8392 5.51771 7.67826 5.47729 7.47728L11.1688 4.18105C11.6127 4.58305 12.1779 4.82411 12.8236 4.82411C14.1555 4.82411 15.2454 3.7386 15.2454 2.41204C15.2454 1.08555 14.1555 0 12.8236 0C11.4917 0 10.4018 1.08555 10.4018 2.41207C10.4018 2.61309 10.442 2.77407 10.4826 2.97505L4.79126 6.27128C4.34722 5.86924 3.78216 5.62822 3.13632 5.62822C1.80439 5.62822 0.754883 6.71373 0.754883 8.0403C0.754883 9.36682 1.84458 10.4524 3.17669 10.4524C3.82254 10.4524 4.3876 10.2113 4.83163 9.80928L10.5634 13.1459C10.5228 13.3065 10.4826 13.4675 10.4826 13.6684C10.4826 14.955 11.5319 16 12.8236 16C14.1153 16 15.1646 14.955 15.1646 13.6684C15.1646 12.3819 14.1153 11.3369 12.8236 11.3369Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport default ShareIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/Smiley.tsx",
    "content": "import * as React from \"react\";\n\nfunction Smiley(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      fill=\"currentColor\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M4.111 2.18a7 7 0 1 1 7.778 11.64A7 7 0 0 1 4.11 2.18zm.556 10.809a6 6 0 1 0 6.666-9.978 6 6 0 0 0-6.666 9.978zM6.5 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm5 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM8 11a3 3 0 0 1-2.65-1.58l-.87.48a4 4 0 0 0 7.12-.16l-.9-.43A3 3 0 0 1 8 11z\"\n      />\n    </svg>\n  );\n}\n\nexport default Smiley;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidBug.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidBug(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M15.113 4.445h-2.497a5.31 5.31 0 00-1.619-1.74l1.449-1.453L11.194 0l-1.93 1.93a5.288 5.288 0 00-2.516 0L4.808 0 3.554 1.252l1.441 1.449a5.357 5.357 0 00-1.608 1.74h-2.5V6.22h1.86a5.875 5.875 0 00-.082.89v.888H.888v1.778h1.778v.89c0 .303.037.596.081.889H.887v1.778h2.498A5.332 5.332 0 008 16a5.332 5.332 0 004.616-2.667h2.497v-1.778h-1.86a5.87 5.87 0 00.082-.89v-.889h1.778V7.998h-1.778V7.11a5.87 5.87 0 00-.082-.889h1.86V4.445z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidBug;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidCalendar.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidCalendar(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#sm-solid-calendar_svg__clip0)\">\n        <path\n          d=\"M14.461.348h-1.538V1.39c0 .192-.138.348-.308.348H12c-.17 0-.308-.156-.308-.348V.348H4.308V1.39c0 .192-.139.348-.308.348h-.615c-.17 0-.308-.156-.308-.348V.348H1.538C.692.348 0 1.13 0 2.087v11.826c0 .957.692 1.74 1.538 1.74h12.923c.847 0 1.539-.783 1.539-1.74V2.087c0-.957-.692-1.74-1.539-1.74zm.308 13.043c0 .479-.346.87-.769.87H2c-.423 0-.77-.391-.77-.87V5.565c0-.191.14-.348.308-.348h12.923c.17 0 .308.157.308.348v7.826zM4.308-.696c0-.19-.139-.347-.308-.347h-.615c-.17 0-.308.156-.308.347V.348h1.23V-.696zm8.615 0c0-.19-.138-.347-.308-.347H12c-.17 0-.308.156-.308.347V.348h1.231V-.696z\"\n          fill=\"currentColor\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"sm-solid-calendar_svg__clip0\">\n          <path fill=\"currentColor\" d=\"M0 0h16v16H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidCalendar;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidCaretRight.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidCaretRight(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M13.7918 8L9.81992 4.03125C9.52617 3.7375 9.52617 3.2625 9.81992 2.97188C10.1137 2.68125 10.5887 2.68125 10.8824 2.97188L15.3824 7.46875C15.6668 7.75313 15.673 8.20938 15.4043 8.50313L10.8855 13.0312C10.7387 13.1781 10.5449 13.25 10.3543 13.25C10.1637 13.25 9.96992 13.1781 9.82305 13.0312C9.5293 12.7375 9.5293 12.2625 9.82305 11.9719L13.7918 8Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidCaretRight;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidChatBubble.tsx",
    "content": "import * as React from \"react\";\n\nfunction SolidChatBubble(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M14.4 0H1.6A1.6 1.6 0 000 1.6V16l3.2-3.2h11.2a1.6 1.6 0 001.6-1.6V1.6A1.6 1.6 0 0014.4 0z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport default SolidChatBubble;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidCompass.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidCompass(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M8.942 8.942L7.058 7.058c-.035-.035-.096-.027-.123.015L5.03 10.861c-.042.07.038.15.108.108l3.788-1.904c.042-.027.05-.088.015-.123z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1.562 9.588l-6.139 3.097c-.07.042-.15-.039-.108-.108l3.1-6.139a.09.09 0 01.027-.026l6.135-3.097c.07-.042.15.039.108.108l-3.1 6.139a.052.052 0 01-.023.026z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport default SvgSolidCompass;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidContributor.tsx",
    "content": "import * as React from \"react\";\n\ninterface SolidContributorProps extends React.SVGProps<SVGSVGElement> {}\n\nfunction SolidContributor(props: SolidContributorProps): JSX.Element {\n  return (\n    <svg\n      width=\"16\"\n      height=\"8\"\n      viewBox=\"0 0 16 8\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M3.96864 7.89354H0.995933V0.106464H3.93846C4.7332 0.106464 5.41979 0.262358 5.99823 0.574145C6.57919 0.883397 7.02686 1.32953 7.34123 1.91255C7.65812 2.49303 7.81656 3.18885 7.81656 4C7.81656 4.81115 7.65938 5.50824 7.345 6.09125C7.03063 6.67174 6.58548 7.11787 6.00955 7.42966C5.43362 7.73891 4.75332 7.89354 3.96864 7.89354ZM3.09343 6.09886H3.89319C4.27547 6.09886 4.60116 6.03675 4.87026 5.91255C5.14188 5.78834 5.34811 5.57414 5.48895 5.26996C5.6323 4.96578 5.70398 4.54246 5.70398 4C5.70398 3.45754 5.63104 3.03422 5.48518 2.73004C5.34182 2.42586 5.13056 2.21166 4.8514 2.08745C4.57475 1.96324 4.23523 1.90114 3.83283 1.90114H3.09343V6.09886ZM0 4.28897V3.1635H4.05918V4.28897H0Z\"\n        fill=\"#5D7290\"\n      />\n      <path\n        d=\"M16 3.02662H13.8723C13.8572 2.84918 13.817 2.68821 13.7516 2.54373C13.6887 2.39924 13.6007 2.27503 13.4875 2.1711C13.3769 2.06464 13.2423 1.98352 13.0839 1.92776C12.9254 1.86945 12.7456 1.8403 12.5444 1.8403C12.1923 1.8403 11.8943 1.92649 11.6503 2.09886C11.4089 2.27123 11.2253 2.51838 11.0996 2.8403C10.9763 3.16223 10.9147 3.5488 10.9147 4C10.9147 4.47655 10.9776 4.87579 11.1033 5.19772C11.2316 5.51711 11.4164 5.75792 11.6579 5.92015C11.8993 6.07985 12.1898 6.1597 12.5293 6.1597C12.723 6.1597 12.8965 6.13561 13.0499 6.08745C13.2033 6.03676 13.3366 5.96451 13.4498 5.87072C13.563 5.77693 13.6548 5.66413 13.7252 5.53232C13.7981 5.39797 13.8472 5.24715 13.8723 5.07985L16 5.09506C15.9749 5.42459 15.8831 5.76046 15.7246 6.10266C15.5662 6.44233 15.3411 6.75665 15.0493 7.04563C14.7601 7.33207 14.4017 7.56274 13.9742 7.73764C13.5466 7.91255 13.0499 8 12.4841 8C11.7748 8 11.1385 7.84664 10.5752 7.53992C10.0143 7.23321 9.57045 6.782 9.2435 6.18631C8.91907 5.59062 8.75685 4.86185 8.75685 4C8.75685 3.13308 8.92284 2.40304 9.25482 1.80989C9.5868 1.2142 10.0345 0.764259 10.5978 0.460077C11.1612 0.153359 11.7899 0 12.4841 0C12.972 0 13.4209 0.0671736 13.8308 0.201521C14.2408 0.335868 14.6004 0.53232 14.9098 0.790875C15.2191 1.0469 15.4681 1.36248 15.6567 1.73764C15.8453 2.1128 15.9598 2.54246 16 3.02662Z\"\n        fill=\"#5D7290\"\n      />\n    </svg>\n  );\n}\n\nexport default SolidContributor;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidDeafened.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidDeafened(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      data-testid=\"headphone-on\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M13.5414 2.33786C12.0154 0.852143 9.99572 0 8 0C6.00429 0 3.98464 0.852143 2.45857 2.33786C0.873214 3.88107 0 5.89286 0 8C0 8.9525 0.3125 10.1818 1.17429 12.4839C2.03607 14.7861 3.75 15.75 4.47393 15.9075C4.68143 15.9529 4.92464 16 5.14286 16C5.51887 16.0003 5.88836 15.9018 6.21429 15.7143L6.71429 15.4286C7.2525 15.1136 7.40964 14.4239 7.1 13.8804L3.99571 8.43143C3.92174 8.30104 3.82264 8.18663 3.70414 8.09481C3.58564 8.003 3.4501 7.93561 3.30537 7.89654C3.16064 7.85748 3.0096 7.84752 2.86099 7.86724C2.71239 7.88696 2.56917 7.93597 2.43964 8.01143L1.95071 8.29714C1.7628 8.40723 1.59395 8.54701 1.45071 8.71107C1.43294 8.73193 1.40953 8.74724 1.38329 8.75516C1.35706 8.76309 1.32909 8.7633 1.30274 8.75577C1.27638 8.74824 1.25275 8.73329 1.23466 8.7127C1.21657 8.69211 1.20479 8.66675 1.20071 8.63964C1.16472 8.42827 1.14537 8.2144 1.14286 8C1.14286 6.20321 1.89286 4.48286 3.25571 3.15679C4.57143 1.87679 6.29964 1.14286 8 1.14286C9.70036 1.14286 11.4286 1.87679 12.7443 3.15679C14.1071 4.48286 14.8571 6.20321 14.8571 8C14.8539 8.21446 14.8339 8.42833 14.7971 8.63964C14.7931 8.66675 14.7813 8.69211 14.7632 8.7127C14.7451 8.73329 14.7215 8.74824 14.6951 8.75577C14.6688 8.7633 14.6408 8.76309 14.6146 8.75516C14.5883 8.74724 14.5649 8.73193 14.5471 8.71107C14.4039 8.54701 14.2351 8.40723 14.0471 8.29714L13.5582 8.01143C13.4287 7.93597 13.2855 7.88696 13.1369 7.86724C12.9883 7.84752 12.8372 7.85748 12.6925 7.89654C12.5478 7.93561 12.4122 8.003 12.2937 8.09481C12.1752 8.18663 12.0761 8.30104 12.0021 8.43143L8.9 13.8804C8.59036 14.4239 8.7475 15.1136 9.28572 15.4286L9.78572 15.7143C10.1116 15.9018 10.4811 16.0003 10.8571 16C11.0754 16 11.3186 15.9529 11.5261 15.9075C12.25 15.75 13.9643 14.7857 14.8257 12.4839C15.6871 10.1821 16 8.9525 16 8C16 5.89286 15.1268 3.88107 13.5414 2.33786Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidDeafenedOff.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidDeafenedOff(\n  props: React.SVGProps<SVGSVGElement>\n) {\n  return (\n    <svg\n      data-testid=\"headphone-off\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M4,8.4C3.9,8.3,3.8,8.2,3.7,8.1C3.6,8,3.5,7.9,3.3,7.9c-0.1,0-0.3,0-0.4,0C2.7,7.9,2.6,7.9,2.4,8L2,8.3\nC1.8,8.4,1.6,8.6,1.5,8.7c0,0,0,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0,0-0.1c0-0.2-0.1-0.4-0.1-0.6c0-0.8,0.1-1.6,0.4-2.3\nL0.7,4.8C0.2,5.8,0,6.9,0,8c0,1,0.3,2.2,1.2,4.5c0.9,2.3,2.6,3.3,3.3,3.4C4.7,16,4.9,16,5.1,16c0.4,0,0.8-0.1,1.1-0.3l0.5-0.3\nc0.5-0.3,0.7-1,0.4-1.5L4,8.4z\"\n      />\n      <path\n        d=\"M8.9,13.9c-0.3,0.6-0.2,1.2,0.4,1.5l0.5,0.3c0.3,0.2,0.7,0.3,1.1,0.3c0.2,0,0.5,0,0.7-0.1c0.1,0,0.1,0,0.2-0.1\nl-2.5-2.5L8.9,13.9z\"\n      />\n      <path\n        d=\"M14.8,12.5C15.7,10.2,16,9,16,8c0-2.1-0.9-4.1-2.5-5.7C12,0.9,10,0,8,0C6.3,0,4.6,0.6,3.2,1.7L1.7,0.3L0.3,1.7\nl14.2,14.2l1.4-1.4l-1.3-1.3C14.6,12.9,14.7,12.7,14.8,12.5z M8,1.2c1.7,0,3.4,0.7,4.7,2c1.4,1.3,2.1,3,2.1,4.8\nc0,0.2,0,0.4-0.1,0.6c0,0,0,0.1,0,0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.1-0.2-0.3-0.3-0.5-0.4L13.6,8\nc-0.1-0.1-0.3-0.1-0.4-0.1c-0.2,0-0.3,0-0.5,0c-0.1,0-0.3,0.1-0.4,0.2c-0.1,0.1-0.2,0.2-0.3,0.3l-0.8,1.4L4,2.6\nC5.2,1.7,6.6,1.2,8,1.2z\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidDiscord.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidDiscord(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M6.3138 6.7124C5.85738 6.7124 5.4978 7.1124 5.4978 7.60082C5.4978 8.08756 5.8658 8.48924 6.3138 8.48924C6.76938 8.48924 7.12896 8.08756 7.12896 7.60082C7.13822 7.11156 6.76938 6.7124 6.3138 6.7124ZM9.23422 6.7124C8.7778 6.7124 8.41822 7.1124 8.41822 7.60082C8.41822 8.08756 8.78622 8.48924 9.23422 8.48924C9.6898 8.48924 10.0494 8.08756 10.0494 7.60082C10.0485 7.11156 9.6898 6.7124 9.23422 6.7124Z\" />\n      <path d=\"M13.1224 0H2.40155C2.18568 0.000552227 1.97204 0.0436171 1.77282 0.126736C1.57359 0.209855 1.39269 0.331399 1.24044 0.48443C1.08819 0.637462 0.967579 0.818982 0.885482 1.01863C0.803385 1.21827 0.761415 1.43213 0.761968 1.648V12.464C0.761968 13.376 1.49797 14.112 2.40155 14.112H11.4735L11.0483 12.6307L12.0731 13.5832L13.0407 14.4792L14.762 16V1.648C14.7625 1.43213 14.7205 1.21827 14.6385 1.01863C14.5564 0.818982 14.4357 0.637462 14.2835 0.48443C14.1312 0.331399 13.9503 0.209855 13.7511 0.126736C13.5519 0.0436171 13.3383 0.000552227 13.1224 0V0ZM10.0335 10.448C10.0335 10.448 9.74555 10.1044 9.50639 9.79874C10.554 9.50232 10.954 8.84716 10.954 8.84716C10.6255 9.06274 10.314 9.216 10.0335 9.32042C9.01265 9.74768 7.88839 9.86427 6.80155 9.65558C6.39995 9.57669 6.00644 9.46112 5.62597 9.31032C5.42531 9.23409 5.23029 9.14376 5.04239 9.04C5.01797 9.02232 4.99439 9.01558 4.97081 8.99958C4.9582 8.99358 4.947 8.98497 4.93797 8.97432C4.79397 8.89516 4.71397 8.83958 4.71397 8.83958C4.71397 8.83958 5.09797 9.47958 6.11439 9.78358C5.87439 10.0867 5.57797 10.448 5.57797 10.448C3.81039 10.3916 3.13923 9.232 3.13923 9.232C3.13923 6.656 4.29123 4.56842 4.29123 4.56842C5.44323 3.70358 6.53881 3.728 6.53881 3.728L6.61881 3.824C5.17881 4.24084 4.51439 4.87242 4.51439 4.87242C4.51439 4.87242 4.69123 4.77642 4.98681 4.64084C5.84239 4.26526 6.52197 4.16084 6.80239 4.13642C6.85039 4.12884 6.89081 4.12042 6.93881 4.12042C8.32593 3.93349 9.73642 4.19711 10.9624 4.87242C10.9624 4.87242 10.33 4.27284 8.96997 3.85684L9.08197 3.72884C9.08197 3.72884 10.1784 3.70442 11.3295 4.56926C11.3295 4.56926 12.4815 6.65684 12.4815 9.23284C12.4815 9.232 11.802 10.3916 10.0335 10.448V10.448Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidDogenitro.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidDogenitro(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M15.132 4.977A4.868 4.868 0 0012.9 2.744C12.03 2.248 10.79 2 9.55 2H8.063v2.977h1.24c.621 0 1.241.124 1.737.248.496.248.869.62 1.117 1.116.248.496.372 1.24.372 2.109 0 .868-.124 1.612-.373 2.108-.247.496-.62.868-1.116.992-.372.248-.992.372-1.612.372H8.063V14.9H9.55c1.364 0 2.48-.248 3.349-.744a4.868 4.868 0 002.232-2.232c.496-.993.745-2.109.745-3.473.123-1.365-.125-2.481-.745-3.473z\" />\n      <path d=\"M8.31 6.713H5.085c-.372 0-.62-.248-.62-.62s.248-.62.62-.62h3.1c.373 0 .62.248.62.62s-.247.62-.495.62zM8.31 11.302H5.085c-.248 0-.62-.248-.62-.62s.248-.62.62-.62h3.1c.373 0 .62.248.62.62s-.247.62-.495.62zM8.31 9.07H.62c-.372 0-.62-.248-.62-.62 0-.373.248-.62.62-.62h7.69c.372 0 .62.247.62.62 0 .372-.372.62-.62.62z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidDogenitro;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidDownload.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidDownload(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M12.9001 6.69999C12.4333 4.40003 10.4333 2.66663 8 2.66663C6.06673 2.66663 4.40007 3.76656 3.56673 5.36666C1.56673 5.56669 0 7.26656 0 9.33329C0 11.5332 1.79997 13.3333 4 13.3333H12.6667C14.5 13.3333 16 11.8333 16 9.99996C16 8.23336 14.6335 6.79993 12.9001 6.69999ZM6.93333 8.39996V5.86663H9.06667V8.39996H11.3333L8 11.7333L4.66667 8.39996H6.93333Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidDownload;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidFriends.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidFriends(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={22}\n      height={16}\n      viewBox=\"0 0 22 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#sm-solid-friends_svg__clip0)\" fill=\"currentColor\">\n        <path d=\"M8.131 8.044a4.011 4.011 0 004.022-4.022C12.153 1.836 10.317 0 8.131 0c-2.273 0-4.11 1.836-4.11 4.022s1.837 4.022 4.11 4.022zm0 1.923C5.421 9.967 0 11.367 0 13.99V16h16.262v-2.011c0-2.623-5.42-4.022-8.13-4.022zM14.339 4.022c0 1.486-.612 2.885-1.574 3.934.175 0 .437.088.612.088a4.011 4.011 0 004.022-4.022C17.399 1.836 15.563 0 13.377 0c-.262 0-.437 0-.612.087.962 1.05 1.574 2.361 1.574 3.935zM16 10.317c.35.175.612.437.874.7.962.874 1.487 1.923 1.487 3.06v2.01h3.147V13.99c0-1.923-2.885-3.148-5.508-3.672z\" />\n      </g>\n      <defs>\n        <clipPath id=\"sm-solid-friends_svg__clip0\">\n          <path fill=\"currentColor\" d=\"M0 0h21.6v16H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidFriends;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidFriendsAdd.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidFriendsAdd(\n  props: React.SVGProps<SVGSVGElement>\n) {\n  return (\n    <svg\n      width={20}\n      height={16}\n      viewBox=\"0 0 20 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M12 8C14.2002 8 16 6.19996 16 4C16 1.80004 14.2002 0 12 0C9.79979 0 8 1.80004 8 4C8 6.19996 9.79979 8 12 8ZM12 10C9.35008 10 4 11.3501 4 14V16H20V14C20 11.3501 14.6499 10 12 10ZM4 6.66667V4H2.66667V6.66667H0V8H2.66667V10.6667H4V8H6.66667V6.66667H4Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidFullscreen.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidFullscreen(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.797 1.559h.934a.78.78 0 000-1.559H.747A.764.764 0 000 .78v3.118a.747.747 0 101.492 0v-1.5l3.144 3.328a.755.755 0 101.066-1.069L2.797 1.56zm10.38.01h-.945a.785.785 0 010-1.569h3.014a.768.768 0 01.753.785v3.143a.755.755 0 11-1.507 0v-1.51l-3.177 3.353a.762.762 0 11-1.078-1.079l2.94-3.123zM2.796 14.441h.934a.78.78 0 010 1.559H.747A.764.764 0 010 15.22v-3.118a.747.747 0 111.492 0v1.5l3.144-3.328a.755.755 0 111.066 1.069L2.797 14.44zm10.38-.01h-.945a.785.785 0 000 1.569h3.014a.767.767 0 00.753-.784v-3.143a.755.755 0 10-1.507 0v1.508l-3.177-3.352a.762.762 0 10-1.078 1.079l2.94 3.123z\"\n      />\n    </svg>\n  );\n}\n\nexport default SvgSolidFullscreen;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidGitHub.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidGitHub(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#clip0)\">\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M8 0C12.4184 0 16 3.67194 16 8.20234C16 11.8255 13.7104 14.8992 10.5336 15.9848C10.128 16.0656 9.984 15.8094 9.984 15.591C9.984 15.3206 9.9936 14.4374 9.9936 13.3398C9.9936 12.575 9.7376 12.0759 9.4504 11.8215C11.232 11.6183 13.104 10.9246 13.104 7.77422C13.104 6.87822 12.7936 6.14706 12.28 5.57266C12.3632 5.36546 12.6376 4.53116 12.2016 3.40156C12.2016 3.40156 11.5312 3.18178 10.004 4.24258C9.35149 4.0607 8.67738 3.96769 8 3.96641C7.32314 3.96763 6.64956 4.06064 5.9976 4.24258C4.4688 3.18178 3.7968 3.40156 3.7968 3.40156C3.3624 4.53116 3.6368 5.36546 3.7192 5.57266C3.208 6.14706 2.8952 6.87822 2.8952 7.77422C2.8952 10.9166 4.7632 11.6209 6.54 11.8281C6.3112 12.0329 6.104 12.3942 6.032 12.9246C5.576 13.1342 4.4176 13.497 3.704 12.2434C3.704 12.2434 3.2808 11.4553 2.4776 11.3977C2.4776 11.3977 1.6976 11.3873 2.4232 11.8961C2.4232 11.8961 2.9472 12.1481 3.3112 13.0961C3.3112 13.0961 3.7808 14.5601 6.0064 14.0641C6.0104 14.7497 6.0176 15.3958 6.0176 15.591C6.0176 15.8078 5.8704 16.0615 5.4712 15.9855C2.292 14.9015 0 11.8263 0 8.20234C0 3.67194 3.5824 0 8 0\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip0\">\n          <rect width=\"16\" height=\"16\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidGoogle.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidGoogle(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#clip0)\">\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M15.8576 6.40316H8.16957C8.16957 7.20316 8.16957 8.8016 8.16397 9.6016H12.6192C12.4488 10.4008 11.8432 11.52 10.988 12.084C10.9872 12.0832 10.9864 12.0887 10.9848 12.0879C9.84796 12.8383 8.34717 13.0088 7.23277 12.7848C5.48637 12.4384 4.10477 11.1721 3.54317 9.56331C3.54637 9.56091 3.54876 9.53835 3.55116 9.53675C3.19996 8.53835 3.19996 7.20236 3.55116 6.40316C4.00316 4.93356 5.42716 3.59301 7.17516 3.22581C8.58236 2.92741 10.1696 3.25061 11.3368 4.34261C11.492 4.19061 13.4848 2.24476 13.6344 2.08636C9.64637 -1.52564 3.26157 -0.254183 0.871965 4.40902L0.867168 4.41761C0.295881 5.52675 -0.00150902 6.75671 -0.000117207 8.00433C0.0012746 9.25195 0.301404 10.4808 0.875164 11.5887L0.867168 11.5953C1.39322 12.6091 2.12661 13.5007 3.01971 14.2125C3.91281 14.9244 4.94565 15.4406 6.05116 15.7274C8.45916 16.3594 11.5256 15.9272 13.5792 14.0696L13.5816 14.0719C15.3216 12.5055 16.4048 9.83516 15.8576 6.40316\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip0\">\n          <rect width=\"16\" height=\"16\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidHelp.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidHelp(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm-.165 11.692c-.454 0-.823-.346-.823-.792 0-.442.369-.792.823-.792.457 0 .827.346.827.792 0 .446-.366.792-.827.792zM9.38 7.965c-.67.389-.896.673-.896 1.166v.304H7.15l-.012-.331c-.065-.792.212-1.285.908-1.692.65-.389.923-.635.923-1.112 0-.477-.461-.827-1.034-.827-.581 0-1 .377-1.031.946H5.538c.027-1.238.943-2.115 2.489-2.115 1.442 0 2.434.8 2.434 1.95 0 .765-.369 1.292-1.08 1.711z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidHelp;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidHome.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidHome(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#sm-solid-home_svg__clip0)\">\n        <path\n          d=\"M15.2 6.08c-.08-.08-.08-.16-.16-.24L9.36.48C8.96.16 8.48 0 8 0c-.4 0-.88.16-1.28.48L1.04 5.76c-.08.08-.16.16-.16.24-.08.16-.08.24-.08.4v8.16c0 .88.8 1.44 1.84 1.44h2.72c.96 0 1.84-.56 1.84-1.52v-.88c-.08-.4.32-.8.8-.8.48 0 .88.4.88.88v.88c0 .96.8 1.52 1.84 1.52h2.72c.96 0 1.84-.56 1.84-1.52V6.4c0-.08 0-.24-.08-.32z\"\n          fill=\"currentColor\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"sm-solid-home_svg__clip0\">\n          <path fill=\"currentColor\" d=\"M0 0h16v16H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidHome;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidInstagram.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidInstagram(\n  props: React.SVGProps<SVGSVGElement>\n) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path d=\"M11.3333 1.33333C12.2167 1.33333 13.0542 1.68333 13.6875 2.3125C14.3208 2.94167 14.6667 3.78333 14.6667 4.66667V11.3333C14.6667 12.2167 14.3167 13.0542 13.6875 13.6875C13.0583 14.3208 12.2167 14.6667 11.3333 14.6667H4.66667C3.78333 14.6667 2.94583 14.3167 2.3125 13.6875C1.67917 13.0583 1.33333 12.2167 1.33333 11.3333V4.66667C1.33333 3.78333 1.68333 2.94583 2.3125 2.3125C2.94167 1.67917 3.78333 1.33333 4.66667 1.33333H11.3333ZM11.3333 0H4.66667C2.1 0 0 2.1 0 4.66667V11.3333C0 13.9 2.1 16 4.66667 16H11.3333C13.9 16 16 13.9 16 11.3333V4.66667C16 2.1 13.9 0 11.3333 0Z\" />\n      <path d=\"M12.3333 4.66699C11.7792 4.66699 11.3333 4.22116 11.3333 3.66699C11.3333 3.11283 11.7792 2.66699 12.3333 2.66699C12.8833 2.66699 13.3333 3.11283 13.3333 3.66699C13.3333 4.22116 12.8833 4.66699 12.3333 4.66699ZM8 5.33366C9.47083 5.33366 10.6667 6.52949 10.6667 8.00033C10.6667 9.47116 9.47083 10.667 8 10.667C6.52917 10.667 5.33333 9.47116 5.33333 8.00033C5.33333 6.52949 6.52917 5.33366 8 5.33366ZM8 4.00033C5.79167 4.00033 4 5.79199 4 8.00033C4 10.2087 5.79167 12.0003 8 12.0003C10.2083 12.0003 12 10.2087 12 8.00033C12 5.79199 10.2083 4.00033 8 4.00033Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidKeyboard.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidKeyboard(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={22}\n      height={16}\n      viewBox=\"0 0 22 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M19.8 0.549805H2.2C1.61652 0.549805 1.05695 0.78159 0.644365 1.19417C0.231785 1.60675 0 2.16633 0 2.7498V13.7498C0 14.3333 0.231785 14.8929 0.644365 15.3054C1.05695 15.718 1.61652 15.9498 2.2 15.9498H19.8C20.3835 15.9498 20.9431 15.718 21.3556 15.3054C21.7682 14.8929 22 14.3333 22 13.7498V2.7498C22 2.16633 21.7682 1.60675 21.3556 1.19417C20.9431 0.78159 20.3835 0.549805 19.8 0.549805ZM12.1 3.8498C12.1 3.24229 12.5925 2.7498 13.2 2.7498C13.8075 2.7498 14.3 3.24229 14.3 3.8498C14.3 4.45732 13.8075 4.9498 13.2 4.9498C12.5925 4.9498 12.1 4.45732 12.1 3.8498ZM12.1 8.2498C12.1 7.64229 12.5925 7.1498 13.2 7.1498C13.8075 7.1498 14.3 7.64229 14.3 8.2498C14.3 8.85732 13.8075 9.3498 13.2 9.3498C12.5925 9.3498 12.1 8.85732 12.1 8.2498ZM7.7 3.8498C7.7 3.24229 8.19249 2.7498 8.8 2.7498C9.40751 2.7498 9.9 3.24229 9.9 3.8498C9.9 4.45732 9.40751 4.9498 8.8 4.9498C8.19249 4.9498 7.7 4.45732 7.7 3.8498ZM7.7 8.2498C7.7 7.64229 8.19249 7.1498 8.8 7.1498C9.40751 7.1498 9.9 7.64229 9.9 8.2498C9.9 8.85732 9.40751 9.3498 8.8 9.3498C8.19249 9.3498 7.7 8.85732 7.7 8.2498ZM3.3 3.8498C3.3 3.24229 3.79249 2.7498 4.4 2.7498C5.00751 2.7498 5.5 3.24229 5.5 3.8498C5.5 4.45732 5.00751 4.9498 4.4 4.9498C3.79249 4.9498 3.3 4.45732 3.3 3.8498ZM3.3 8.2498C3.3 7.64229 3.79249 7.1498 4.4 7.1498C5.00751 7.1498 5.5 7.64229 5.5 8.2498C5.5 8.85732 5.00751 9.3498 4.4 9.3498C3.79249 9.3498 3.3 8.85732 3.3 8.2498ZM16.5 12.7498C16.5 13.3021 16.0523 13.7498 15.5 13.7498H6.5C5.94772 13.7498 5.5 13.3021 5.5 12.7498V12.5498C5.5 11.9975 5.94772 11.5498 6.5 11.5498H15.5C16.0523 11.5498 16.5 11.9975 16.5 12.5498V12.7498ZM18.7 8.2498C18.7 8.85732 18.2075 9.3498 17.6 9.3498C16.9925 9.3498 16.5 8.85732 16.5 8.2498C16.5 7.64229 16.9925 7.1498 17.6 7.1498C18.2075 7.1498 18.7 7.64229 18.7 8.2498ZM18.7 3.8498C18.7 4.45732 18.2075 4.9498 17.6 4.9498C16.9925 4.9498 16.5 4.45732 16.5 3.8498C16.5 3.24229 16.9925 2.7498 17.6 2.7498C18.2075 2.7498 18.7 3.24229 18.7 3.8498Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidKeyboard;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidLink.tsx",
    "content": "import * as React from \"react\";\n\ninterface SolidLinkProps extends React.SVGProps<SVGSVGElement> {}\n\nfunction SolidLink(props: SolidLinkProps): JSX.Element {\n  return (\n    <svg\n      width=\"16\"\n      height=\"8\"\n      viewBox=\"0 0 16 8\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M1.52143 4C1.52143 2.63214 2.63214 1.52143 4 1.52143H7.14286V0H4C1.79286 0 0 1.79286 0 4C0 6.20714 1.79286 8 4 8H7.14286V6.47857H4C2.63214 6.47857 1.52143 5.36786 1.52143 4ZM4.57143 4.78571H11.4286V3.21429H4.57143V4.78571ZM12 0H8.85714V1.52143H12C13.3679 1.52143 14.4786 2.63214 14.4786 4C14.4786 5.36786 13.3679 6.47857 12 6.47857H8.85714V8H12C14.2071 8 16 6.20714 16 4C16 1.79286 14.2071 0 12 0Z\"\n        fill=\"#FD4D4D\"\n      />\n    </svg>\n  );\n}\n\nexport default SolidLink;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidLogOut.tsx",
    "content": "function SvgSolidLogOut(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M5.53845 8.82301H12.8346L11.1577 10.5422L12.3077 11.6922L16 7.99993L12.3077 4.30762L11.1154 5.45762L12.8346 7.17685H5.53845V8.82301V8.82301Z\" />\n      <path d=\"M7.98846 14.3577C6.29231 14.3577 4.7 13.6962 3.5 12.4962C2.3 11.2962 1.64231 9.7 1.64231 8C1.64231 6.30385 2.30385 4.70385 3.5 3.50385C4.7 2.30385 6.29231 1.64231 7.98846 1.64231C9.68077 1.64231 11.2692 2.3 12.4692 3.49615L13.6346 2.33077C13.3077 2.00769 12.95 1.70769 12.5692 1.43846C11.2192 0.5 9.63846 0 7.98846 0C3.58462 0 0 3.58846 0 8C0 12.4115 3.58462 16 7.98846 16C9.63846 16 11.2192 15.5 12.5654 14.5577C12.95 14.2885 13.3038 13.9923 13.6308 13.6654L12.4692 12.5038C11.2731 13.7 9.68077 14.3577 7.98846 14.3577ZM15.3848 8.03258L15.3521 7.99996L15.3848 7.96731L15.4174 7.99996L15.3848 8.03258Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidLogOut;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidMegaphone.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidMegaphone(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M12.504 3.979l-.825.821a2.91 2.91 0 01.853 2.057c0 .8-.328 1.532-.853 2.057l.825.825a4.074 4.074 0 000-5.76z\" />\n      <path d=\"M14.15 2.393l-.81.81a5.159 5.159 0 011.51 3.654 5.156 5.156 0 01-1.51 3.654l.81.81A6.322 6.322 0 0016 6.857c0-1.74-.707-3.318-1.85-4.464zm-3.864 3.04V1.713H9.143L5.714 4.572H1.143L0 5.142v4l1.143.572L4 14.286h1.714L4.643 9.714h1.071L9.143 12h1.143V8.282c.657-.06 1.143-.675 1.143-1.425s-.486-1.364-1.143-1.425z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidMegaphone;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidMessages.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidMessages(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#sm-solid-messages_svg__clip0)\">\n        <path d=\"M14.538 4.707H6.01c-.808 0-1.466.658-1.466 1.466v5.937c0 .808.658 1.462 1.466 1.462h4.879a.42.42 0 01.292.123l2.425 2.236c.134.13.357.077.357-.112v-1.947c0-.23.147-.304.377-.304h.039c.808 0 1.62-.65 1.62-1.462V6.173a1.46 1.46 0 00-1.462-1.466z\" />\n        <path d=\"M4.859 3.762h6.826V1.25c0-.693-.562-1.255-1.255-1.255H1.25C.557-.005-.005.557-.005 1.25v6.356c0 .693.562 1.255 1.255 1.255H3.6V5.017A1.26 1.26 0 014.86 3.762z\" />\n      </g>\n      <defs>\n        <clipPath id=\"sm-solid-messages_svg__clip0\">\n          <path d=\"M0 0h16v16H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidMessages;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidMicrophone.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidMicrophone(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      data-testid=\"mic-on\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M8 11.205a3.2 3.2 0 003.196-3.197V3.213C11.196 1.442 9.77 0 8.016 0a.746.746 0 00-.167.02 3.201 3.201 0 00-3.046 3.193v4.795a3.2 3.2 0 003.196 3.197z\" />\n      <path d=\"M7.2 14.346V16H8.8v-1.654c3.148-.395 5.594-3.083 5.594-6.338h-1.598A4.8 4.8 0 018 12.803a4.8 4.8 0 01-4.795-4.795H1.606c0 3.255 2.447 5.943 5.595 6.338z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidMicrophone;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidMicrophoneOff.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidMicrophoneOff(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      data-testid=\"mic-off\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#solid-microphone-off_svg__clip0)\" fill=\"currentColor\">\n        <path d=\"M15.758 14.636l-2.718-2.717a6.338 6.338 0 001.353-3.91h-1.598c0 .994-.313 1.963-.894 2.77L10.743 9.62a3.138 3.138 0 00.454-1.612V3.213C11.197 1.442 9.77 0 8.017 0a.74.74 0 00-.167.02 3.201 3.201 0 00-3.047 3.193v.469L1.373.25.243 1.38l14.385 14.385 1.13-1.13zM3.205 8.008H1.607c0 3.255 2.445 5.943 5.594 6.339V16h1.598v-1.653a6.34 6.34 0 001.791-.504l-1.238-1.237c-.439.13-.894.197-1.352.197a4.8 4.8 0 01-4.795-4.795z\" />\n        <path d=\"M4.81 8.062A3.194 3.194 0 007.946 11.2L4.809 8.062z\" />\n      </g>\n      <defs>\n        <clipPath id=\"solid-microphone-off_svg__clip0\">\n          <path fill=\"currentColor\" d=\"M0 0h16v16H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidMicrophoneOff;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidMoon.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidMoon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M5.62095 2.89044C5.62095 1.86455 5.82769 0.889365 6.20216 0C2.6447 0.86206 0.00390625 4.06456 0.00390625 7.88337C0.00390625 12.3614 3.63938 15.9969 8.11741 15.9969C11.9362 15.9969 15.1387 13.3561 16.0008 9.79862C15.1114 10.1731 14.1323 10.3798 13.1103 10.3798C8.97557 10.3798 5.62095 7.02521 5.62095 2.89044Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidNew.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidNew(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g\n        clipPath=\"url(#sm-solid-new_svg__clip0)\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        fill=\"currentColor\"\n      >\n        <path d=\"M8-8a2 2 0 012 2v28a2 2 0 11-4 0V-6a2 2 0 012-2z\" />\n        <path d=\"M-8 8a2 2 0 012-2h28a2 2 0 110 4H-6a2 2 0 01-2-2z\" />\n      </g>\n      <defs>\n        <clipPath id=\"sm-solid-new_svg__clip0\">\n          <rect width={16} height={16} rx={8} fill=\"currentColor\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidNew;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidNotification.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidNotification(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M8.16 16c.877 0 1.594-.72 1.594-1.6H6.568c0 .88.717 1.6 1.593 1.6zm5.177-4.8V6.8c0-2.44-1.712-4.52-3.982-5.04V1.2c0-.68-.517-1.2-1.194-1.2-.677 0-1.195.52-1.195 1.2v.56c-2.27.52-3.982 2.6-3.982 5.04v4.4l-1.592 1.6v.8H14.93v-.8l-1.593-1.6z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidNotification;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidPersonAdd.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidPersonAdd(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M9.6 8.4C11.3602 8.4 12.8 6.95997 12.8 5.2C12.8 3.44003 11.3602 2 9.6 2C7.83983 2 6.4 3.44003 6.4 5.2C6.4 6.95997 7.83983 8.4 9.6 8.4ZM9.6 10C7.48007 10 3.2 11.0801 3.2 13.2V14.8H16V13.2C16 11.0801 11.7199 10 9.6 10ZM3.2 7.33333V5.2H2.13333V7.33333H0V8.4H2.13333V10.5333H3.2V8.4H5.33333V7.33333H3.2Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidPersonAdd;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidPlus.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidMessages(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#clip12)\">\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M8 0C8.55228 0 9 0.447715 9 1V15C9 15.5523 8.55228 16 8 16C7.44772 16 7 15.5523 7 15V1C7 0.447715 7.44772 0 8 0Z\"\n          fill=\"#DEE3EA\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M0.000976562 8C0.000976562 7.44772 0.448692 7 1.00098 7H15.001C15.5533 7 16.001 7.44772 16.001 8C16.001 8.55228 15.5533 9 15.001 9H1.00098C0.448692 9 0.000976562 8.55228 0.000976562 8Z\"\n          fill=\"#DEE3EA\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip12\">\n          <rect width=\"16\" height=\"16\" fill=\"white\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidMessages;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidRocket.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidRocket(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g clipPath=\"url(#clip0)\">\n        <path d=\"M15.9159 0.21672C15.9005 0.151338 15.8543 0.10134 15.7889 0.0859561C13.6967 -0.425562 8.86229 1.39744 6.24316 4.01657C5.73164 4.52809 5.31243 5.03191 4.97014 5.52035C4.16248 5.44728 3.35482 5.50882 2.66638 5.8088C0.724152 6.66262 0.15879 8.8933 0.00110395 9.85095C-0.037356 10.0779 0.151098 10.274 0.378012 10.2509L3.49712 9.90864C3.50096 10.2086 3.52019 10.4471 3.53942 10.6125C3.55481 10.774 3.62788 10.924 3.74326 11.0394L4.95091 12.247C5.06629 12.3624 5.21628 12.4355 5.37781 12.4508C5.54319 12.4701 5.78164 12.4893 6.07778 12.4932L5.73549 15.6084C5.71241 15.8353 5.90856 16.0238 6.13547 15.9853C7.09313 15.8315 9.32765 15.2661 10.1776 13.3239C10.4776 12.6355 10.5391 11.8316 10.4699 11.0278C10.9584 10.6855 11.466 10.2663 11.9775 9.7548C14.6082 7.13952 16.4197 2.41279 15.9159 0.21672V0.21672ZM9.63918 6.36647C8.9969 5.72419 8.9969 4.68577 9.63918 4.04349C10.2815 3.40121 11.3199 3.40121 11.9622 4.04349C12.6044 4.68577 12.6044 5.72419 11.9622 6.36647C11.3199 7.00876 10.2815 7.00876 9.63918 6.36647Z\" />\n        <path\n          d=\"M4.86632 12.7893C4.70479 12.9508 4.41634 13.0431 4.10481 13.097C3.40484 13.2162 2.79333 12.6047 2.91255 11.9047C2.9664 11.5932 3.05486 11.3047 3.21639 11.147L3.22023 11.1432C3.30869 11.0547 3.23562 10.9086 3.11254 10.924C2.73564 10.9701 2.36642 11.1394 2.07797 11.4278C1.38569 12.1201 1.32031 14.6892 1.32031 14.6892C1.32031 14.6892 3.89328 14.6238 4.58556 13.9315C4.87786 13.6392 5.04323 13.2739 5.08939 12.897C5.10092 12.7739 4.95093 12.7008 4.86632 12.7893V12.7893Z\"\n          fill=\"#FD4D4D\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip0\">\n          <rect width=\"16\" height=\"16\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidRocket;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidSearch.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidSearch(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g\n        clipPath=\"url(#sm-solid-search_svg__clip0)\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n      >\n        <path d=\"M7.212 1.803a5.409 5.409 0 100 10.818 5.409 5.409 0 000-10.818zM0 7.212a7.212 7.212 0 1114.424 0A7.212 7.212 0 010 7.212z\" />\n        <path d=\"M11.03 11.03a.901.901 0 011.275 0l3.43 3.432a.902.902 0 01-1.274 1.275l-3.431-3.431a.901.901 0 010-1.275z\" />\n      </g>\n      <defs>\n        <clipPath id=\"sm-solid-search_svg__clip0\">\n          <path d=\"M0 0h16v16H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n\nexport default SvgSolidSearch;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidSettings.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidSettings(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M14.075 8.8c.041-.24.041-.52.041-.8s-.04-.52-.04-.8l1.717-1.32c.164-.12.205-.32.082-.52L14.24 2.6c-.082-.16-.327-.24-.491-.16l-2.046.8c-.409-.32-.9-.6-1.39-.8L10.024.32a.438.438 0 00-.41-.32H6.344a.438.438 0 00-.41.32l-.326 2.12c-.491.2-.941.48-1.392.8l-2.045-.8c-.205-.08-.41 0-.491.16L.042 5.36c-.081.16-.04.4.082.52l1.76 1.32c0 .28-.041.52-.041.8s.04.52.04.8L.166 10.12c-.164.12-.205.32-.082.52L1.72 13.4c.082.16.327.24.491.16l2.046-.8c.409.32.9.6 1.39.8l.328 2.12c.04.2.204.32.409.32h3.273c.204 0 .368-.16.409-.32l.327-2.12c.491-.2.941-.48 1.391-.8l2.046.8c.204.08.409 0 .49-.16l1.637-2.76c.082-.16.041-.4-.082-.52l-1.8-1.32zm-6.096 2c-1.595 0-2.863-1.24-2.863-2.8 0-1.56 1.268-2.8 2.863-2.8 1.596 0 2.864 1.24 2.864 2.8 0 1.56-1.268 2.8-2.864 2.8z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidSettings;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidSimpleMegaphone.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidSimpleMegaphone(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M4.56369 12.8417C4.74667 13.2878 5.10933 13.6316 5.56144 13.7847L8.91241 14.9174C9.28726 15.0414 9.69345 15.0254 10.0577 14.8723C10.4219 14.7192 10.7203 14.4392 10.8989 14.0826L11.968 11.9138L16 13.1081V0L4.92308 3.5371V9.36291L5.53682 9.58512L4.6121 11.461C4.50678 11.6742 4.44794 11.9078 4.43959 12.146C4.43124 12.3843 4.47356 12.6216 4.56369 12.8417ZM7.09415 10.1494L10.4107 11.3512L9.43098 13.3386L6.08 12.2059L7.09415 10.1494ZM1.64103 9.36291H3.28205V3.5371H1.64103C0.736 3.5371 0 4.28363 0 5.20162V7.69839C0 8.61637 0.736 9.36291 1.64103 9.36291Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidSimpleMegaphone;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidStaff.tsx",
    "content": "import * as React from \"react\";\n\ninterface SolidStaffProps extends React.SVGProps<SVGSVGElement> {}\n\nfunction SolidStaff(props: SolidStaffProps): JSX.Element {\n  return (\n    <svg\n      width=\"16\"\n      height=\"9\"\n      viewBox=\"0 0 16 9\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M4.21511 8.89714H1.05779V0.12H4.18306C5.02715 0.12 5.75638 0.295715 6.37075 0.647143C6.98779 0.995714 7.46326 1.49857 7.79716 2.15571C8.13373 2.81 8.30201 3.59429 8.30201 4.50857C8.30201 5.42286 8.13506 6.20857 7.80116 6.86571C7.46727 7.52 6.99447 8.02286 6.38277 8.37429C5.77107 8.72286 5.04852 8.89714 4.21511 8.89714ZM3.28554 6.87429H4.13498C4.541 6.87429 4.88691 6.80429 5.17273 6.66429C5.46122 6.52429 5.68025 6.28286 5.82984 5.94C5.98209 5.59714 6.05822 5.12 6.05822 4.50857C6.05822 3.89714 5.98076 3.42 5.82583 3.07714C5.67357 2.73429 5.4492 2.49286 5.1527 2.35286C4.85887 2.21286 4.49826 2.14286 4.07087 2.14286H3.28554V6.87429ZM0 4.83429V3.56571H4.31128V4.83429H0Z\"\n        fill=\"#5D7290\"\n      />\n      <path\n        d=\"M13.7562 2.86286C13.7348 2.57714 13.6347 2.35429 13.4557 2.19429C13.2794 2.03429 13.011 1.95429 12.6503 1.95429C12.4206 1.95429 12.2323 1.98429 12.0854 2.04429C11.9412 2.10143 11.8343 2.18 11.7649 2.28C11.6954 2.38 11.6593 2.49429 11.6567 2.62286C11.6513 2.72857 11.6687 2.82429 11.7088 2.91C11.7515 2.99286 11.8183 3.06857 11.9091 3.13714C11.9999 3.20286 12.1161 3.26286 12.2577 3.31714C12.3993 3.37143 12.5675 3.42 12.7625 3.46286L13.4357 3.61714C13.8898 3.72 14.2784 3.85571 14.6016 4.02429C14.9249 4.19286 15.1893 4.39143 15.395 4.62C15.6007 4.84571 15.7516 5.1 15.8477 5.38286C15.9466 5.66571 15.9973 5.97429 16 6.30857C15.9973 6.88572 15.8624 7.37429 15.5953 7.77429C15.3282 8.17429 14.9462 8.47857 14.4494 8.68714C13.9552 8.89571 13.3609 9 12.6664 9C11.9532 9 11.3308 8.88714 10.7992 8.66143C10.2703 8.43571 9.85897 8.08857 9.56514 7.62C9.27398 7.14857 9.12707 6.54571 9.1244 5.81143H11.24C11.2533 6.08 11.3161 6.30571 11.4283 6.48857C11.5405 6.67143 11.6981 6.81 11.9011 6.90429C12.1068 6.99857 12.3512 7.04571 12.6343 7.04571C12.8721 7.04571 13.0711 7.01429 13.2313 6.95143C13.3916 6.88857 13.5131 6.80143 13.5959 6.69C13.6787 6.57857 13.7215 6.45143 13.7242 6.30857C13.7215 6.17429 13.6801 6.05714 13.5999 5.95714C13.5225 5.85429 13.3943 5.76286 13.2153 5.68286C13.0363 5.6 12.7946 5.52286 12.4901 5.45143L11.6727 5.26286C10.9461 5.09429 10.3732 4.81286 9.9538 4.41857C9.53709 4.02143 9.33008 3.48 9.33275 2.79429C9.33008 2.23714 9.46898 1.75 9.74945 1.33286C10.0326 0.912857 10.4239 0.585715 10.9234 0.351429C11.4256 0.117143 12.0013 0 12.6503 0C13.3128 0 13.8858 0.118572 14.3692 0.355714C14.8527 0.592857 15.2254 0.927143 15.4871 1.35857C15.7516 1.78714 15.8851 2.28857 15.8878 2.86286H13.7562Z\"\n        fill=\"#5D7290\"\n      />\n    </svg>\n  );\n}\n\nexport default SolidStaff;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidTime.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidTime(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm.538 8.692A.54.54 0 018 9.231H4.308a.54.54 0 01-.539-.539.54.54 0 01.539-.538h3.154V3.077A.54.54 0 018 2.538a.54.54 0 01.538.539v5.615z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidTime;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidTrash.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidTrash(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M2.66634 14.2262C2.66634 15.2019 3.46626 16 4.44414 16H11.5553C12.5331 16 13.333 15.2019 13.333 14.2262V4H2.66634V14.2262ZM14.6664 1.33333H11.333L10.217 0H5.78243L4.66634 1.33333H1.33301V2.66667H14.6664V1.33333Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidTwitter.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidTwitter(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M5.032 14.4012C11.0696 14.4012 14.372 9.47632 14.372 5.20512C14.372 5.06512 14.372 4.92594 14.3624 4.78754C15.0036 4.33131 15.5581 3.76446 16 3.11333C15.4004 3.37544 14.7652 3.54695 14.1152 3.6227C14.7976 3.22189 15.3106 2.58639 15.5584 1.83481C14.9125 2.21162 14.2077 2.47667 13.4736 2.61879C12.8713 1.99295 12.0479 1.62777 11.1797 1.60161C10.3116 1.57544 9.46767 1.89051 8.8288 2.47895C8.41122 2.86346 8.10326 3.35201 7.93642 3.89458C7.76957 4.43714 7.74983 5.01414 7.8792 5.56684C6.57888 5.50477 5.30574 5.17231 4.1409 4.59106C2.97606 4.00981 1.94507 3.19235 1.1136 2.19067C0.694601 2.90216 0.566459 3.74793 0.755901 4.55161C0.945344 5.35528 1.43774 6.05489 2.1304 6.50434C1.60863 6.48953 1.09782 6.35116 0.64 6.10043V6.14106C0.6408 7.67946 1.7424 9.00423 3.2736 9.30903C2.79023 9.43904 2.2837 9.45818 1.792 9.3645C2.2216 10.6813 3.4544 11.5826 4.8584 11.609C3.47737 12.6756 1.73254 13.1571 0 12.9493C1.50623 13.8992 3.25124 14.4019 5.032 14.3989\"\n        fill=\"white\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/SolidUser.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidUser(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M8 8c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zm0 2c-2.65 0-8 1.35-8 4v2h16v-2c0-2.65-5.35-4-8-4z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidUser;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidVolume.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidVolume(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M9.431 1.1a1.002 1.002 0 00-1.056.119L4.3 4.5H1c-.55 0-1 .45-1 1v5c0 .55.45 1 1 1h3.3l4.075 3.281c.181.144.406.219.625.219a1 1 0 001-1V2a.986.986 0 00-.569-.9zm4.719 2.313a.736.736 0 00-1.05-.007.756.756 0 00-.006 1.063 5.017 5.017 0 011.425 3.537 5.04 5.04 0 01-1.425 3.538.756.756 0 00.006 1.062.735.735 0 001.05-.006A6.523 6.523 0 0016 8.012h-.012c0-1.75-.644-3.38-1.838-4.6z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidVolume;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidVolumeOff.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidVolumeOff(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M13.1594 16.0025C12.9708 16.0025 12.7862 15.9063 12.6861 15.7331L3.91007 0.816517C3.75617 0.558737 3.84466 0.224008 4.10629 0.0739568C4.36792 -0.0760943 4.70265 0.00854987 4.85655 0.270177L13.6326 15.1868C13.7865 15.4446 13.698 15.7793 13.4364 15.9294C13.3517 15.9794 13.2556 16.0025 13.1594 16.0025V16.0025ZM3.26754 5.84515H1.22839C0.885963 5.84515 0.612793 6.12217 0.612793 6.46075V9.53872C0.612793 9.87729 0.88981 10.1543 1.22839 10.1543H3.26754L5.78378 12.1742C5.89536 12.2627 6.03387 12.3089 6.17238 12.3089C6.26472 12.3089 6.35321 12.2897 6.43785 12.2473C6.65331 12.1434 6.78797 11.928 6.78797 11.6933V6.91859L5.22975 4.27154L3.26754 5.84515ZM15.3871 7.99973C15.3871 5.71819 14.4906 3.579 12.867 1.97845C12.6861 1.79762 12.3937 1.80147 12.2129 1.9823C12.0321 2.16313 12.0359 2.45554 12.2167 2.63637C13.6672 4.06378 14.4637 5.96827 14.4637 7.99973C14.4637 9.70801 13.8981 11.3239 12.8593 12.6475L13.3594 13.4939C14.6714 11.9665 15.3871 10.0389 15.3871 7.99973Z\" />\n      <path d=\"M12.9712 7.9997C12.9712 6.32606 12.3287 4.75629 11.1668 3.57897C10.9859 3.39814 10.6935 3.39429 10.5127 3.57512C10.3319 3.75595 10.328 4.04836 10.5089 4.22919C11.5015 5.23338 12.0479 6.5723 12.0479 8.00355C12.0479 8.8346 11.8632 9.63872 11.5131 10.3582L12.0594 11.2854C12.6519 10.3082 12.9712 9.18088 12.9712 7.9997ZM10.3704 7.9997C10.3704 6.93011 9.95868 5.92592 9.21612 5.17567C9.03529 4.99484 8.74288 4.99099 8.56205 5.17182C8.53896 5.1949 8.51973 5.21799 8.50049 5.24492L10.3511 8.39215C10.3627 8.26133 10.3704 8.13052 10.3704 7.9997Z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidVolumeOff;\n"
  },
  {
    "path": "kibbeh/src/icons/SolidWarning.tsx",
    "content": "import * as React from \"react\";\n\nexport default function SvgSolidWarning(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M6.95727 1.22762L0.147124 13.6558C-0.283558 14.4326 0.289402 15.3824 1.18922 15.3824H14.8134C15.7093 15.3824 16.2823 14.4326 15.8555 13.6558L9.04146 1.22762C8.59156 0.412407 7.40718 0.412407 6.95727 1.22762V1.22762ZM8.67615 6.38426L8.53772 11.0756H7.46102L7.32258 6.38426H8.67615ZM7.99937 13.6289C7.58791 13.6289 7.2649 13.3174 7.2649 12.9214C7.2649 12.5253 7.58791 12.2138 7.99937 12.2138C8.41082 12.2138 8.73383 12.5253 8.73383 12.9214C8.73383 13.3174 8.41082 13.6289 7.99937 13.6289Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/icons/WinCloseIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction WinCloseIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 15 15\"\n      fill=\"currentColor\"\n      {...props}\n    >\n      <g id=\"Layer_2\" data-name=\"Layer 2\">\n        <g id=\"svg8\">\n          <g id=\"g926\">\n            <g id=\"path920\">\n              <path d=\"M14.34,15a.65.65,0,0,1-.46-.19L.19,1.12a.67.67,0,0,1,0-.93.67.67,0,0,1,.93,0L14.81,13.88a.67.67,0,0,1,0,.93A.66.66,0,0,1,14.34,15Z\" />\n            </g>\n            <g id=\"path922\">\n              <path d=\"M.65,15a.65.65,0,0,1-.46-.19.67.67,0,0,1,0-.93L13.88.19a.67.67,0,0,1,.93,0,.67.67,0,0,1,0,.93L1.12,14.81A.66.66,0,0,1,.65,15Z\" />\n            </g>\n          </g>\n        </g>\n      </g>\n    </svg>\n  );\n}\n\nexport default WinCloseIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/WinMaximizeIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction WinMaximizeIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 15 15\"\n      fill=\"currentColor\"\n      {...props}\n    >\n      <g id=\"Layer_2\" data-name=\"Layer 2\">\n        <g id=\"svg8\">\n          <g id=\"rect81\">\n            <path d=\"M15,15H0V0H15ZM1.24,13.76H13.76V1.24H1.24Z\" />\n          </g>\n        </g>\n      </g>\n    </svg>\n  );\n}\n\nexport default WinMaximizeIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/WinMinimizeIcon.tsx",
    "content": "import * as React from \"react\";\n\nfunction WinMinimizeIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"15\"\n      height=\"1.24\"\n      viewBox=\"0 0 15 1.24\"\n      fill=\"currentColor\"\n      {...props}\n    >\n      <g id=\"Layer_2\" data-name=\"Layer 2\">\n        <g id=\"svg8\">\n          <g id=\"rect917\">\n            <rect width=\"15\" height=\"1.24\" />\n          </g>\n        </g>\n      </g>\n    </svg>\n  );\n}\n\nexport default WinMinimizeIcon;\n"
  },
  {
    "path": "kibbeh/src/icons/badges/ContributorBadge.tsx",
    "content": "import * as React from \"react\";\n\ninterface SvgSolidContributorBadgeProps extends React.SVGProps<SVGSVGElement> {\n  contributions: number;\n}\n\nfunction SvgSolidContributorBadge(props: SvgSolidContributorBadgeProps) {\n  const fill = props.style?.color || \"#5df4df\";\n  let variant = (\n    <path\n      d=\"M8 0L0 8l8 8 8-8-8-8zm-.8 5.1L4.3 8l2.9 2.9v2.6L1.7 8l5.5-5.5v2.6zm1.6 5.8L11.7 8 8.8 5.1V2.5L14.3 8l-5.5 5.5v-2.6z\"\n      fill={fill}\n    />\n  );\n\n  if (props.contributions >= 1000) {\n    variant = (\n      <>\n        <path\n          fill={fill}\n          d=\"M11.098 15.716l4.596-4.596.495.495-4.596 4.596zM-.005 4.624L4.59.028l.495.495L.49 5.12zM8.1.1l-8 8 8 8 8-8-8-8zm-.8 13.5L1.8 8.1l5.5-5.5v2.6L4.4 8.1 7.3 11v2.6zM7 9.5l.2-1.2-.8-.9 1.2-.2.5-1.1.5 1.1 1.2.2-.8.9.2 1.2-1.1-.6-1.1.6zM9 11l2.9-2.9L9 5.2V2.6l5.5 5.5L9 13.6V11zM11.092.518l.495-.495 4.597 4.596-.495.495zM0 11.62l.496-.494 4.596 4.596-.495.495z\"\n        />\n        <path\n          fill={fill}\n          d=\"M11.098 15.716l4.596-4.596.495.495-4.596 4.596zM-.005 4.624L4.59.028l.495.495L.49 5.12zM8.1.1l-8 8 8 8 8-8-8-8zm-.8 13.5L1.8 8.1l5.5-5.5v2.6L4.4 8.1 7.3 11v2.6zM7 9.5l.2-1.2-.8-.9 1.2-.2.5-1.1.5 1.1 1.2.2-.8.9.2 1.2-1.1-.6-1.1.6zM9 11l2.9-2.9L9 5.2V2.6l5.5 5.5L9 13.6V11zM11.092.518l.495-.495 4.597 4.596-.495.495zM0 11.62l.496-.494 4.596 4.596-.495.495z\"\n        />\n      </>\n    );\n  } else if (props.contributions >= 500) {\n    variant = (\n      <path\n        fill={fill}\n        d=\"M8.1.1l-8 8 8 8 8-8-8-8zm-.8 5.1L4.4 8.1 7.3 11v2.6L1.8 8.1l5.5-5.5v2.6zM9 11l2.9-2.9L9 5.2V2.6l5.5 5.5L9 13.6V11zM11.098 15.716l4.596-4.596.495.495-4.596 4.596zM-.005 4.624L4.59.028l.495.495L.49 5.12zM11.092.518l.495-.495 4.597 4.596-.495.495zM0 11.62l.496-.494 4.596 4.596-.495.495z\"\n      />\n    );\n  } else if (props.contributions >= 250) {\n    variant = (\n      <path\n        fill={fill}\n        d=\"M8.1.1l-8 8 8 8 8-8-8-8zm-.8 5.1L4.4 8.1 7.3 11v2.6L1.8 8.1l5.5-5.5v2.6zM9 11l2.9-2.9L9 5.2V2.6l5.5 5.5L9 13.6V11zM11.098 15.716l4.596-4.596.495.495-4.596 4.596zM-.005 4.624L4.59.028l.495.495L.49 5.12zM0 11.62l.496-.494 4.596 4.596-.495.495z\"\n      />\n    );\n  } else if (props.contributions >= 150) {\n    variant = (\n      <path\n        fill={fill}\n        d=\"M8.1.1l-8 8 8 8 8-8-8-8zm-.8 5.1L4.4 8.1 7.3 11v2.6L1.8 8.1l5.5-5.5v2.6zM9 11l2.9-2.9L9 5.2V2.6l5.5 5.5L9 13.6V11zM11.098 15.715l4.596-4.596.495.495-4.596 4.596zM-.005 4.624L4.59.028l.495.495L.49 5.12z\"\n      />\n    );\n  } else if (props.contributions >= 50) {\n    variant = (\n      <path\n        fill={fill}\n        d=\"M8.1.1l-8 8 8 8 8-8-8-8zm-.8 5.1L4.4 8.1 7.3 11v2.6L1.8 8.1l5.5-5.5v2.6zM9 11l2.9-2.9L9 5.2V2.6l5.5 5.5L9 13.6V11zM-.005 4.624L4.59.028l.495.495L.49 5.12z\"\n      />\n    );\n  }\n\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      {variant}\n    </svg>\n  );\n}\n\nexport default SvgSolidContributorBadge;\n"
  },
  {
    "path": "kibbeh/src/icons/badges/StaffBadge.tsx",
    "content": "import * as React from \"react\";\n\nfunction SvgSolidStaffBadge(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width={16}\n      height={16}\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <g id=\"StaffBadge_svg__KMcxqn_tif_1_\">\n        <linearGradient\n          id=\"StaffBadge_svg__SVGID_1_\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1={1.523}\n          y1={8}\n          x2={14.477}\n          y2={8}\n        >\n          <stop offset={0} stopColor=\"#fd4d4d\" />\n          <stop offset={1} stopColor=\"#f26767\" />\n        </linearGradient>\n        <path\n          d=\"M1.5 2.3c.6-.1 1.1-.2 1.6-.3 1.3-.3 2.6-.8 3.8-1.4l.9-.6c.2 0 .3 0 .4.1.7.4 1.4.8 2.1 1.1 1.1.5 2.2.8 3.4 1 .2 0 .5.1.7.1v7.1c0 .5-.1.9-.3 1.4-.4.9-1 1.7-1.8 2.4-.9.8-1.8 1.5-2.9 2.1-.4.2-.8.4-1.3.7h-.2c-1.2-.6-2.3-1.2-3.3-2-.8-.6-1.6-1.3-2.1-2.1-.4-.6-.8-1.3-.9-2v-.4-5.3c-.1-.7-.1-1.3-.1-1.9zm11.7 4.1V3.3c0-.1 0-.1-.1-.1-1-.2-2.1-.5-3-.9-.7-.3-1.4-.7-2-1h-.2c-.3.2-.5.3-.8.5-1.4.6-2.8 1.1-4.2 1.4-.1 0-.1.1-.1.2V9.7c.1.5.3.9.5 1.3.5.8 1.1 1.4 1.9 2 .9.7 1.8 1.3 2.8 1.8h.1c.1-.1.2-.1.4-.2.9-.5 1.8-1 2.5-1.7.7-.6 1.4-1.3 1.9-2.1.3-.5.5-1 .4-1.6-.1-.9-.1-1.8-.1-2.8z\"\n          fill={props.style?.color || \"url(#StaffBadge_svg__SVGID_1_)\"}\n        />\n      </g>\n      <linearGradient\n        id=\"StaffBadge_svg__SVGID_2_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1={4.592}\n        y1={8}\n        x2={11.408}\n        y2={8}\n      >\n        <stop offset={0} stopColor=\"#fd4d4d\" />\n        <stop offset={1} stopColor=\"#f26767\" />\n      </linearGradient>\n      <path\n        d=\"M11.4 7.9V5v-.1c-.1 0-.3 0-.4-.1-.6-.1-1.2-.3-1.8-.5-.4-.1-.7-.3-1.1-.5h-.2c-.1.1-.3.2-.5.3-.6.3-1.3.6-2 .8-.2 0-.5 0-.8.1v3.9c.1.5.3.8.5 1.1.3.4.7.8 1.1 1.1.5.4 1.1.7 1.7 1H8c.2-.1.5-.2.7-.4.6-.3 1.1-.7 1.5-1.1.4-.4.7-.8.9-1.2.1-.2.2-.5.2-.7.1-.2.1-.5.1-.8z\"\n        fill={props.style?.color || \"url(#StaffBadge_svg__SVGID_2_)\"}\n      />\n      <path fill=\"none\" d=\"M0 0h16v16H0z\" />\n    </svg>\n  );\n}\n\nexport default SvgSolidStaffBadge;\n"
  },
  {
    "path": "kibbeh/src/icons/badges/index.tsx",
    "content": "export { default as StaffBadge } from \"./StaffBadge\";\nexport { default as ContributorBadge } from \"./ContributorBadge\";\n"
  },
  {
    "path": "kibbeh/src/icons/index.tsx",
    "content": "export { default as SolidCalendar } from \"./SolidCalendar\";\nexport { default as SolidContributor } from \"./SolidContributor\";\nexport { default as SolidCompass } from \"./SolidCompass\";\nexport { default as SolidFriends } from \"./SolidFriends\";\nexport { default as SolidHome } from \"./SolidHome\";\nexport { default as SolidMessages } from \"./SolidMessages\";\nexport { default as SolidNew } from \"./SolidNew\";\nexport { default as SolidNotification } from \"./SolidNotification\";\nexport { default as SolidPlus } from \"./SolidPlus\";\nexport { default as SolidSearch } from \"./SolidSearch\";\nexport { default as LgLogo } from \"./LgLogo\";\nexport { default as LogoIcon } from \"./LogoIcon\";\nexport { default as OutlineGlobe } from \"./OutlineGlobe\";\nexport { default as SolidBug } from \"./SolidBug\";\nexport { default as SolidCaretRight } from \"./SolidCaretRight\";\nexport { default as SolidDogenitro } from \"./SolidDogenitro\";\nexport { default as SolidFullscreen } from \"./SolidFullscreen\";\nexport { default as SolidHelp } from \"./SolidHelp\";\nexport { default as SolidLogOut } from \"./SolidLogOut\";\nexport { default as SolidDiscord } from \"./SolidDiscord\";\nexport { default as SolidLink } from \"./SolidLink\";\nexport { default as SolidMegaphone } from \"./SolidMegaphone\";\nexport { default as SolidSimpleMegaphone } from \"./SolidSimpleMegaphone\";\nexport { default as SolidMicrophone } from \"./SolidMicrophone\";\nexport { default as SolidMicrophoneOff } from \"./SolidMicrophoneOff\";\nexport { default as SolidSettings } from \"./SolidSettings\";\nexport { default as SolidStaff } from \"./SolidStaff\";\nexport { default as SolidTime } from \"./SolidTime\";\nexport { default as SolidUser } from \"./SolidUser\";\nexport { default as SolidVolume } from \"./SolidVolume\";\nexport { default as SolidVolumeOff } from \"./SolidVolumeOff\";\nexport { default as SolidRocket } from \"./SolidRocket\";\nexport { default as Smiley } from \"./Smiley\";\nexport { default as WinMaximizeIcon } from \"./WinMaximizeIcon\";\nexport { default as WinMinimizeIcon } from \"./WinMinimizeIcon\";\nexport { default as WinCloseIcon } from \"./WinCloseIcon\";\nexport { default as MacMaximizeIcon } from \"./MacMaximizeIcon\";\nexport { default as MacMinimizeIcon } from \"./MacMinimizeIcon\";\nexport { default as MacCloseIcon } from \"./MacCloseIcon\";\nexport { default as SolidChatBubble } from \"./SolidChatBubble\";\nexport { default as SolidInstagram } from \"./SolidInstagram\";\nexport { default as SolidPersonAdd } from \"./SolidPersonAdd\";\nexport { default as ShareIcon } from \"./Share\";\nexport { default as TwitterIcon } from \"./SolidTwitter\";\nexport { default as LinkIcon } from \"./Link\";\nexport { default as SolidTrash } from \"./SolidTrash\";\nexport { default as SolidMoon } from \"./SolidMoon\";\nexport { default as SolidWarning } from \"./SolidWarning\";\nexport { default as SolidDeafenedOff } from \"./SolidDeafenedOff\";\nexport { default as SolidDeafened } from \"./SolidDeafened\";\nexport { default as SolidFriendsAdd } from \"./SolidFriendsAdd\";\nexport { default as BotIcon } from \"./BotIcon\";\nexport { default as SolidDownload } from \"./SolidDownload\";\nexport { default as DeveloperIcon } from \"./DeveloperIcon\";\n"
  },
  {
    "path": "kibbeh/src/jest.config.js",
    "content": "module.exports = {\n  // The root of your source code, typically /src\n  // `<rootDir>` is a token Jest substitutes\n  roots: [\"<rootDir>/src\"],\n\n  // Jest transformations -- this adds support for TypeScript\n  // using ts-jest\n  transform: {\n    \"^.+\\\\.tsx?$\": \"ts-jest\",\n  },\n\n  // Runs special logic, such as cleaning up components\n  // when using React Testing Library and adds special\n  // extended assertions to Jest\n  setupFilesAfterEnv: [\n    \"@testing-library/react/cleanup-after-each\",\n    \"@testing-library/jest-dom/extend-expect\",\n  ],\n\n  // Test spec file resolution pattern\n  // Matches parent folder `__tests__` and filename\n  // should contain `test` or `spec`.\n  testRegex: \"(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.tsx?$\",\n\n  // Module file extensions for importing\n  moduleFileExtensions: [\"ts\", \"tsx\", \"js\", \"jsx\", \"json\", \"node\"],\n};\n"
  },
  {
    "path": "kibbeh/src/lib/constants.ts",
    "content": "export const __prod__ = process.env.NODE_ENV === \"production\";\nexport const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL!;\nexport const isStaging = process.env.NEXT_PUBLIC_IS_STAGING === \"true\";\nexport const baseUrl =\n  process.env.NEXT_PUBLIC_BASE_URL || \"https://dogehouse.tv\";\nexport const loginNextPathKey = \"@dogehouse/login-next\";\n\nexport const linkRegex = /(^|\\s)(https?:\\/\\/)(www\\.)?([-a-z0-9]{1,63}\\.)*?[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\\.[a-z]{1,6}(\\/[-\\\\w@\\\\+\\\\.~#\\\\?&/=%]*)?[^\\s()]+/;\nexport const codeBlockRegex = /`([^`]*)`/g;\nexport const mentionRegex = /^(?!.*\\bRT\\b)(?:.+\\s)?#?@\\w+/i;\n"
  },
  {
    "path": "kibbeh/src/lib/createChatMessage.ts",
    "content": "import { BaseUser } from \"@dogehouse/kebab\";\nimport normalizeUrl from \"normalize-url\";\nimport { linkRegex, codeBlockRegex, mentionRegex } from \"./constants\";\n\nexport const createChatMessage = (\n  message: string,\n  roomUsers: BaseUser[] = []\n) => {\n  const tokens = ([] as unknown) as [\n    {\n      t: string;\n      v: string;\n    }\n  ];\n\n  const whisperedToUsernames: string[] = [];\n\n  const testAndPushToken = (item: string) => {\n    const isLink = linkRegex.test(item);\n    const withoutAt = item.replace(/@|#/g, \"\");\n    const isMention =\n      roomUsers.find((m) => withoutAt === m.username) &&\n      mentionRegex.test(item);\n\n    // whisperedTo users list\n    if (isMention && item.startsWith(\"#@\")) {\n      whisperedToUsernames.push(withoutAt);\n    }\n\n    if (isLink) {\n      tokens.push({\n        t: \"link\",\n        v: normalizeUrl(item),\n      });\n    } else if (isMention) {\n      tokens.push({\n        t: \"mention\",\n        v: withoutAt,\n      });\n    } else if (item.startsWith(\":\") && item.endsWith(\":\") && item.length > 2) {\n      tokens.push({\n        t: \"emote\",\n        v: item.slice(1, item.length - 1).toLowerCase(),\n      });\n    } else {\n      tokens.push({\n        t: \"text\",\n        v: item,\n      });\n    }\n  };\n\n  const match = message.matchAll(new RegExp(codeBlockRegex, \"g\"));\n  let matchResult = match.next();\n\n  // For message that matches the regex pattern of code blocks.\n  if (!matchResult.done) {\n    const splitMessage = message.split(codeBlockRegex);\n\n    splitMessage.forEach((text, index) => {\n      // First and last index is empty string while split using the code block regex.\n      if (!index && index === splitMessage.length - 1) {\n        return;\n      }\n\n      const trimmed = text.trim();\n\n      if (!matchResult.done && text === matchResult.value[1]) {\n        if (trimmed) {\n          tokens.push({\n            t: \"block\",\n            v: trimmed,\n          });\n        } else {\n          tokens.push({\n            t: \"text\",\n            v: matchResult.value[0],\n          });\n        }\n\n        matchResult = match.next();\n      } else {\n        text.split(\" \").forEach((item) => {\n          testAndPushToken(item);\n        });\n      }\n    });\n  } else {\n    message.split(\" \").forEach((item) => testAndPushToken(item));\n  }\n\n  return {\n    tokens,\n    whisperedTo: roomUsers\n      .filter((u) =>\n        whisperedToUsernames\n          .map((x) => x?.toLowerCase())\n          .includes(u.username?.toLowerCase())\n      )\n      .map((u) => u.id),\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/lib/defaultQueryFn.ts",
    "content": "import { useTokenStore } from \"../modules/auth/useTokenStore\";\nimport { apiBaseUrl } from \"./constants\";\nimport fetch from \"isomorphic-fetch\";\n\nexport const defaultQueryFn = async ({ queryKey }: { queryKey: string }) => {\n  const { accessToken, refreshToken } = useTokenStore.getState();\n\n  const r = await fetch(`${apiBaseUrl}${queryKey}`, {\n    headers: {\n      \"X-Access-Token\": accessToken,\n      \"X-Refresh-Token\": refreshToken,\n    },\n  });\n\n  if (r.status !== 200) {\n    throw new Error(await r.text());\n  }\n  const _accessToken = r.headers.get(\"access-token\");\n  const _refreshToken = r.headers.get(\"refresh-token\");\n\n  if (_accessToken && _refreshToken) {\n    useTokenStore.getState().setTokens({\n      accessToken: _accessToken,\n      refreshToken: _refreshToken,\n    });\n  }\n\n  return await r.json();\n};\n"
  },
  {
    "path": "kibbeh/src/lib/i18n.ts",
    "content": "import i18n from \"i18next\";\nimport Backend from \"i18next-http-backend\";\nimport LanguageDetector from \"i18next-browser-languagedetector\";\nimport { initReactI18next } from \"react-i18next\";\nimport isDate from \"lodash/isDate\";\nimport { __prod__ } from \"./constants\";\n\nconst DETECTION_OPTIONS = {\n  order: [\"localStorage\", \"navigator\"],\n};\n\nfunction createDateFormatOptions(format: string): Intl.DateTimeFormatOptions {\n  switch (format) {\n    case \"intlDate\": {\n      // EN returns 3/16/2021, 5:45 PM\n      return {\n        year: \"numeric\",\n        month: \"numeric\",\n        day: \"numeric\",\n        hour: \"numeric\",\n        minute: \"numeric\",\n      };\n    }\n    case \"intlTime\": {\n      // EN returns 05:45 PM\n      return {\n        hour: \"numeric\",\n        minute: \"numeric\",\n      };\n    }\n    default: {\n      // EN returns Tuesday, March 16, 2021, 5:45 PM\n      return {\n        weekday: \"long\",\n        year: \"numeric\",\n        month: \"long\",\n        day: \"numeric\",\n        hour: \"numeric\",\n        minute: \"numeric\",\n      };\n    }\n  }\n}\n\nexport const init_i18n = () => {\n  i18n\n    // import & load translations from -> /public/locales\n    .use(Backend)\n    // https://github.com/i18next/i18next-browser-languageDetector\n    .use(LanguageDetector)\n    // pass the i18n instance to react-i18next.\n    .use(initReactI18next)\n    // init i18next\n    // see opts @ https://www.i18next.com/overview/configuration-options\n    .init({\n      detection: DETECTION_OPTIONS,\n      fallbackLng: \"en\",\n      debug: !__prod__,\n      interpolation: {\n        escapeValue: false,\n        format: (value, format, lng) => {\n          return isDate(value) && format\n            ? new Intl.DateTimeFormat(lng, createDateFormatOptions(format))\n                .format(value)\n                .toString()\n            : value;\n        },\n      },\n      react: {\n        useSuspense: false, // fixes 'no fallback UI was specified' in react i18next when using hooks\n      },\n    });\n};\n"
  },
  {
    "path": "kibbeh/src/lib/isCurrentRoomId.ts",
    "content": "import { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\n\nexport const isCurrentRoomId = (id: string) =>\n  id && id === useCurrentRoomIdStore.getState().currentRoomId;\n"
  },
  {
    "path": "kibbeh/src/lib/isServer.ts",
    "content": "export const isServer = typeof window === \"undefined\";\n"
  },
  {
    "path": "kibbeh/src/lib/isWebRTCEnabled.ts",
    "content": "export const isWebRTCEnabled = () =>\n  [\n    \"RTCPeerConnection\",\n    \"webkitRTCPeerConnection\",\n    \"mozRTCPeerConnection\",\n    \"RTCIceGatherer\",\n  ].some((item) => window && item in window);\n"
  },
  {
    "path": "kibbeh/src/lib/kFormatter.ts",
    "content": "export const kFormatter = (num: number) => {\n  if (num < 1000) {\n    return `${num}`;\n  }\n\n  const base = Math.floor(Math.log(Math.abs(num)) / Math.log(1000));\n  const suffix = \"kmb\"[base - 1];\n  const abbrev = String(num / 1000 ** base).substring(0, 3);\n  return (abbrev.endsWith(\".\") ? abbrev.slice(0, -1) : abbrev) + suffix;\n};\n"
  },
  {
    "path": "kibbeh/src/lib/queryClient.ts",
    "content": "import { QueryClient } from \"react-query\";\nimport { showErrorToast } from \"./showErrorToast\";\nimport { defaultQueryFn } from \"./defaultQueryFn\";\n\nexport const queryClient = new QueryClient({\n  defaultOptions: {\n    mutations: {\n      onError: (e) => {\n        if (\"message\" in (e as Error)) {\n          showErrorToast((e as Error).message);\n        }\n      },\n    },\n    queries: {\n      retry: false,\n      staleTime: 60 * 1000 * 5,\n      onError: (e) => {\n        if (\"message\" in (e as Error)) {\n          showErrorToast((e as Error).message);\n        }\n      },\n      queryFn: defaultQueryFn as any,\n    },\n  },\n});\n"
  },
  {
    "path": "kibbeh/src/lib/roomToCurrentRoom.ts",
    "content": "import { Room, CurrentRoom } from \"@dogehouse/kebab\";\n\nexport const roomToCurrentRoom = (r: Room): CurrentRoom =>\n  r\n    ? {\n        ...r,\n        muteMap: {},\n        deafMap: {},\n        users: [],\n        activeSpeakerMap: {},\n        autoSpeaker: false,\n      }\n    : r;\n"
  },
  {
    "path": "kibbeh/src/lib/showErrorToast.ts",
    "content": "import { useErrorToastStore } from \"../modules/errors/useErrorToastStore\";\n\nexport const showErrorToast = (m: string) => {\n  console.log(\"showErrorToast: \", m);\n  useErrorToastStore.getState().showToast({ message: m });\n};\n"
  },
  {
    "path": "kibbeh/src/lib/tests/constants.test.ts",
    "content": "import { linkRegex } from \"../constants\";\n\ndescribe(\"Link Regex\", () => {\n  test(\"Only Link\", () => {\n    const msg1 = \"https://abc.com\";\n\n    expect(linkRegex.test(msg1)).toBeTruthy();\n  });\n\n  test(\"Link between text\", () => {\n    const msg2 = \"some text https://abc.com other text\";\n\n    expect(linkRegex.test(msg2)).toBeTruthy();\n  });\n\n  test(\"Link in brackets\", () => {\n    const msg3 = \"(https://abc.com)\";\n\n    expect(linkRegex.test(msg3)).toBeFalsy();\n  });\n\n  test(\"Link with parameters\", () => {\n    const msg4 = \"text after https://abc.com/queries?parameter text after\";\n\n    expect(linkRegex.test(msg4)).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/lib/tests/kFormatter.test.ts",
    "content": "import { kFormatter } from \"../kFormatter\";\n\ndescribe(\"Number formatter\", () => {\n  it(\"should return 1\", () => {\n    expect(kFormatter(1)).toBe(\"1\");\n  });\n\n  it(\"should return 1k\", () => {\n    expect(kFormatter(1000)).toBe(\"1k\");\n  });\n\n  it(\"should return 3.8k\", () => {\n    expect(kFormatter(3821)).toBe(\"3.8k\");\n  });\n\n  it(\"should return 9.9k\", () => {\n    expect(kFormatter(9999)).toBe(\"9.9k\");\n  });\n\n  it(\"should return 10k\", () => {\n    expect(kFormatter(10500)).toBe(\"10k\");\n  });\n\n  it(\"should return 101k\", () => {\n    expect(kFormatter(101800)).toBe(\"101k\");\n  });\n\n  it(\"should return 3m\", () => {\n    expect(kFormatter(3000000)).toBe(\"3m\");\n  });\n\n  it(\"should return 3.8m\", () => {\n    expect(kFormatter(3800000)).toBe(\"3.8m\");\n  });\n\n  it(\"should return 98m\", () => {\n    expect(kFormatter(98150000)).toBe(\"98m\");\n  });\n\n  it(\"should return 124m\", () => {\n    expect(kFormatter(124200000)).toBe(\"124m\");\n  });\n\n  it(\"should return 9.9m\", () => {\n    expect(kFormatter(9999999)).toBe(\"9.9m\");\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/lib/validateStruct.ts",
    "content": "import { Struct } from \"superstruct\";\n\nexport const validateStruct = <T>(struct: Struct<T>) => (values: T) => {\n  const errors: Record<string, string> = {};\n  const [result] = struct.validate(values);\n  for (const failure of result?.failures() || []) {\n    errors[failure.path[0]] = failure.message;\n  }\n  return errors;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/admin/AdminPage.tsx",
    "content": "import * as React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { AdminPageForm } from \"./AdminPageForm\";\n\ninterface Props {}\n\nexport const AdminPage: PageComponent<Props> = () => {\n  return (\n    <WaitForWsAndAuth>\n      <DefaultDesktopLayout>\n        <AdminPageForm />\n      </DefaultDesktopLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nAdminPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/admin/AdminPageForm.tsx",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useRouter } from \"next/router\";\nimport React, { useEffect, useState } from \"react\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { Input } from \"../../ui/Input\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\n\ninterface SearchUsersProps {}\n\nexport const AdminPageForm: React.FC<SearchUsersProps> = ({}) => {\n  const conn = useConn();\n  const [username, setUsername] = useState(\"\");\n  const [reason, setReason] = useState(\"\");\n  const [contributions, setContributions] = useState(0);\n  const [isStaff, setIsStaff] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const { replace } = useRouter();\n  const wrapper = wrap(conn);\n\n  useEffect(() => {\n    if (conn.user.username !== \"benawad\") {\n      showErrorToast(\"nice try\");\n      replace(\"/dash\");\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  if (conn.user.username !== \"benawad\") {\n    return <MiddlePanel />;\n  }\n\n  return (\n    <MiddlePanel>\n      <h3 className=\"text-primary-100\">{t(\"pages.admin.username\")}</h3>\n      <div className=\"flex\">\n        <Input\n          autoFocus\n          placeholder={t(\"pages.admin.usernamePlaceholder\")}\n          value={username}\n          onChange={(e) => setUsername(e.target.value)}\n        />\n      </div>\n      <div className=\"mt-6\">\n        <h3 className=\"text-primary-100\">{t(\"pages.admin.ban\")}</h3>\n        <Input\n          className={`mb-4`}\n          autoFocus\n          placeholder={t(\"pages.admin.reason\")}\n          value={reason}\n          onChange={(e) => setReason(e.target.value)}\n        />\n        <Button\n          onClick={() => {\n            if (username && reason) {\n              wrapper.mutation.ban(username, reason);\n            }\n          }}\n        >\n          {t(\"pages.admin.ban\")}\n        </Button>\n      </div>\n    </MiddlePanel>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/auth/WaitForWsAndAuth.tsx",
    "content": "import React, { useContext } from \"react\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { useVerifyLoggedIn } from \"./useVerifyLoggedIn\";\n\ninterface WaitForWsAndAuthProps {}\n\nexport const WaitForWsAndAuth: React.FC<WaitForWsAndAuthProps> = ({\n  children,\n}) => {\n  const { conn } = useContext(WebSocketContext);\n\n  if (!useVerifyLoggedIn()) {\n    return null;\n  }\n\n  if (!conn) {\n    // @todo make this better\n    return <div className=\"flex\">loading...</div>;\n  }\n\n  return <>{children}</>;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/auth/useSaveTokensFromQueryParams.ts",
    "content": "import { useEffect } from \"react\";\nimport { useRouter } from \"next/router\";\nimport { useTokenStore } from \"./useTokenStore\";\nimport { loginNextPathKey } from \"../../lib/constants\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\n\nexport const useSaveTokensFromQueryParams = () => {\n  const { query: params, push } = useRouter();\n\n  useEffect(() => {\n    if (typeof params.error === \"string\" && params.error) {\n      showErrorToast(params.error);\n    }\n    if (\n      typeof params.accessToken === \"string\" &&\n      typeof params.refreshToken === \"string\" &&\n      params.accessToken &&\n      params.refreshToken\n    ) {\n      useTokenStore.getState().setTokens({\n        accessToken: params.accessToken,\n        refreshToken: params.refreshToken,\n      });\n      let nextPath = \"/dash\";\n      try {\n        const possibleNextPath = localStorage.getItem(loginNextPathKey);\n        if (possibleNextPath && possibleNextPath.startsWith(\"/\")) {\n          nextPath = possibleNextPath;\n          localStorage.setItem(loginNextPathKey, \"\");\n        }\n      } catch {}\n      // Push to next path after auto redirect to /dash (100 msecs is unoticeable)\n      setTimeout(() => push(nextPath), 100);\n    }\n  }, [params, push]);\n};\n"
  },
  {
    "path": "kibbeh/src/modules/auth/useTokenStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { isServer } from \"../../lib/isServer\";\n\nconst accessTokenKey = \"@toum/token\";\nconst refreshTokenKey = \"@toum/refresh-token\";\n\nconst getDefaultValues = () => {\n  if (!isServer) {\n    try {\n      return {\n        accessToken: localStorage.getItem(accessTokenKey) || \"\",\n        refreshToken: localStorage.getItem(refreshTokenKey) || \"\",\n      };\n    } catch {}\n  }\n\n  return {\n    accessToken: \"\",\n    refreshToken: \"\",\n  };\n};\n\nexport const useTokenStore = create(\n  combine(getDefaultValues(), (set) => ({\n    setTokens: (x: { accessToken: string; refreshToken: string }) => {\n      try {\n        localStorage.setItem(accessTokenKey, x.accessToken);\n        localStorage.setItem(refreshTokenKey, x.refreshToken);\n      } catch {}\n\n      set(x);\n    },\n  }))\n);\n"
  },
  {
    "path": "kibbeh/src/modules/auth/useVerifyLoggedIn.ts",
    "content": "import { useRouter } from \"next/router\";\nimport { useEffect } from \"react\";\nimport { useTokenStore } from \"./useTokenStore\";\n\nexport const useVerifyLoggedIn = () => {\n  const { replace, asPath } = useRouter();\n  const hasTokens = useTokenStore((s) => !!(s.accessToken && s.refreshToken));\n\n  useEffect(() => {\n    if (!hasTokens) {\n      replace(`/?next=${asPath}`);\n    }\n  }, [hasTokens, asPath, replace]);\n\n  return hasTokens;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/dashboard/CreateRoomModal.tsx",
    "content": "import { Form, Formik } from \"formik\";\nimport { useRouter } from \"next/router\";\nimport React from \"react\";\nimport { InputField } from \"../../form-fields/InputField\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafePrefetch } from \"../../shared-hooks/useTypeSafePrefetch\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { ButtonLink } from \"../../ui/ButtonLink\";\nimport { Modal } from \"../../ui/Modal\";\nimport { NativeSelect } from \"../../ui/NativeSelect\";\nimport { useRoomChatStore } from \"../room/chat/useRoomChatStore\";\n\ninterface CreateRoomModalProps {\n  onRequestClose: () => void;\n  data?: {\n    name: string;\n    description: string;\n    privacy: string;\n  };\n  edit?: boolean;\n}\n\nexport const CreateRoomModal: React.FC<CreateRoomModalProps> = ({\n  onRequestClose,\n  data,\n  edit,\n}) => {\n  const conn = useWrappedConn();\n  const { t } = useTypeSafeTranslation();\n  const { push } = useRouter();\n  const prefetch = useTypeSafePrefetch();\n\n  return (\n    <Modal isOpen onRequestClose={onRequestClose}>\n      <Formik<{\n        name: string;\n        privacy: string;\n        description: string;\n      }>\n        initialValues={\n          data\n            ? data\n            : {\n                name: \"\",\n                description: \"\",\n                privacy: \"public\",\n              }\n        }\n        validateOnChange={false}\n        validateOnBlur={false}\n        validate={({ name, description }) => {\n          const errors: Record<string, string> = {};\n\n          if (name.length < 2 || name.length > 60) {\n            return {\n              name: t(\"components.modals.createRoomModal.nameError\"),\n            };\n          } else if (description.length > 500) {\n            return {\n              description: t(\n                \"components.modals.createRoomModal.descriptionError\"\n              ),\n            };\n          }\n\n          return errors;\n        }}\n        onSubmit={async ({ name, privacy, description }) => {\n          const d = { name, privacy, description };\n          const resp = edit\n            ? await conn.mutation.editRoom(d)\n            : await conn.mutation.createRoom(d);\n\n          if (typeof resp === \"object\" && \"error\" in resp) {\n            showErrorToast(resp.error);\n\n            return;\n          } else if (resp.room) {\n            const { room } = resp;\n\n            prefetch([\"joinRoomAndGetInfo\", room.id], [room.id]);\n            console.log(\"new room voice server id: \" + room.voiceServerId);\n            useRoomChatStore.getState().clearChat();\n            useCurrentRoomIdStore.getState().setCurrentRoomId(room.id);\n            push(`/room/[id]`, `/room/${room.id}`);\n          }\n\n          onRequestClose();\n        }}\n      >\n        {({ setFieldValue, values, isSubmitting }) => (\n          <Form className={`grid grid-cols-3 gap-4 focus:outline-none w-full`}>\n            <div className={`col-span-3 block`}>\n              <h4 className={`mb-2 text-primary-100`}>\n                {edit ? t(\"pages.home.editRoom\") : t(\"pages.home.createRoom\")}\n              </h4>\n              <div className={`text-primary-300`}>\n                {t(\"components.modals.createRoomModal.subtitle\")}\n              </div>\n            </div>\n            <div className={`flex h-full w-full col-span-2`}>\n              <InputField\n                className={`rounded-8 bg-primary-700 h-6`}\n                name=\"name\"\n                maxLength={60}\n                placeholder={t(\"components.modals.createRoomModal.roomName\")}\n                autoFocus\n                autoComplete=\"off\"\n              />\n            </div>\n            <div className={`grid items-start grid-cols-1 h-6`}>\n              <NativeSelect\n                value={values.privacy}\n                onChange={(e) => {\n                  setFieldValue(\"privacy\", e.target.value);\n                }}\n              >\n                <option value=\"public\" className={`hover:bg-primary-900`}>\n                  {t(\"components.modals.createRoomModal.public\")}\n                </option>\n                <option value=\"private\" className={`hover:bg-primary-900`}>\n                  {t(\"components.modals.createRoomModal.private\")}\n                </option>\n              </NativeSelect>\n            </div>\n            <div className={`flex col-span-3 bg-primary-700 rounded-8`}>\n              <InputField\n                className={`h-11 col-span-3 w-full`}\n                name=\"description\"\n                rows={3}\n                maxLength={500}\n                placeholder={t(\n                  \"components.modals.createRoomModal.roomDescription\"\n                )}\n                textarea\n              />\n            </div>\n\n            <div className={`flex pt-2 space-x-3 col-span-full items-center`}>\n              <Button loading={isSubmitting} type=\"submit\" className={`mr-3`}>\n                {edit ? t(\"common.save\") : t(\"pages.home.createRoom\")}\n              </Button>\n              <ButtonLink type=\"button\" onClick={onRequestClose}>\n                {t(\"common.cancel\")}\n              </ButtonLink>\n            </div>\n          </Form>\n        )}\n      </Formik>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/dashboard/DashboardPage.tsx",
    "content": "import React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { FeedController } from \"./FeedController\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\ninterface LoungePageProps {}\n\nexport const DashboardPage: PageComponent<LoungePageProps> = ({}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title={t(\"header.dashboard\")} />\n      <DefaultDesktopLayout>\n        <FeedController />\n      </DefaultDesktopLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nDashboardPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/dashboard/FeedController.tsx",
    "content": "import isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport React, { useContext, useEffect, useState } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useDownloadAlertStore } from \"../../global-stores/useDownloadAlertStore\";\nimport { isServer } from \"../../lib/isServer\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { useTypeSafePrefetch } from \"../../shared-hooks/useTypeSafePrefetch\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"../../ui/Button\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { FeedHeader } from \"../../ui/FeedHeader\";\nimport { RoomCard } from \"../../ui/RoomCard\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { useRoomChatStore } from \"../room/chat/useRoomChatStore\";\nimport { EditScheduleRoomModalController } from \"../scheduled-rooms/EditScheduleRoomModalController\";\nimport { ScheduledRoomCard } from \"../scheduled-rooms/ScheduledRoomCard\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { CreateRoomModal } from \"./CreateRoomModal\";\n\ninterface FeedControllerProps {}\n\nconst Page = ({\n  cursor,\n  isLastPage,\n  onLoadMore,\n}: {\n  cursor: number;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n  onLoadMore: (o: number) => void;\n}) => {\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const { push } = useRouter();\n  const prefetch = useTypeSafePrefetch();\n  const { t } = useTypeSafeTranslation();\n  const shouldAlert = useDownloadAlertStore().shouldAlert;\n  const { isLoading, data } = useTypeSafeQuery(\n    [\"getTopPublicRooms\", cursor],\n    {\n      staleTime: Infinity,\n      enabled: !isServer,\n      refetchOnMount: \"always\",\n      refetchInterval: 10000,\n    },\n    [cursor]\n  );\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@rpc/page\", {\n        page: \"home\",\n        opened: true,\n        modal: false,\n        data: data?.rooms.length,\n      });\n\n      return () => {\n        ipcRenderer.send(\"@rpc/page\", {\n          page: \"home\",\n          opened: false,\n          modal: false,\n          data: data?.rooms.length,\n        });\n      };\n    }\n  }, [data]);\n\n  // useEffect(() => {\n  //   if (shouldAlert && !isElectron()) {\n  //     showErrorToast(\n  //       t(\"pages.home.desktopAlert\"),\n  //       \"sticky\",\n  //       <BannerButton\n  //         onClick={() => {\n  //           window.location.href = window.location.origin + \"/download\";\n  //         }}\n  //       >\n  //         Download\n  //       </BannerButton>,\n  //       () => {\n  //         localStorage.setItem(\"@baklava/showDownloadAlert\", \"false\");\n  //       }\n  //     );\n  //   }\n  // }, []);\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (!data) {\n    return null;\n  }\n\n  // if (isOnlyPage && data.rooms.length === 0) {\n  //   return (\n  //     <Button variant=\"small\" onClick={() => refetch()}>\n  //       {t(\"pages.home.refresh\")}\n  //     </Button>\n  //   );\n  // }\n\n  return (\n    <>\n      {data.rooms.map((room) => (\n        <RoomCard\n          onClick={() => {\n            if (room.id !== currentRoomId) {\n              useRoomChatStore.getState().reset();\n              prefetch([\"joinRoomAndGetInfo\", room.id], [room.id]);\n            }\n\n            push(`/room/[id]`, `/room/${room.id}`);\n          }}\n          key={room.id}\n          title={room.name}\n          subtitle={\n            \"peoplePreviewList\" in room\n              ? room.peoplePreviewList\n                  .slice(0, 3)\n                  .map((x) => x.displayName)\n                  .join(\", \")\n              : \"\"\n          }\n          avatars={\n            \"peoplePreviewList\" in room\n              ? room.peoplePreviewList\n                  .map((x) => x.avatarUrl!)\n                  .slice(0, 3)\n                  .filter((x) => x !== null)\n              : []\n          }\n          listeners={\"numPeopleInside\" in room ? room.numPeopleInside : 0}\n          tags={[]}\n        />\n      ))}\n      {isLastPage && data.nextCursor ? (\n        <div className={`flex justify-center py-5`}>\n          <Button\n            size=\"small\"\n            onClick={() => {\n              onLoadMore(data.nextCursor!);\n            }}\n          >\n            {t(\"common.loadMore\")}\n          </Button>\n        </div>\n      ) : null}\n    </>\n  );\n};\n\n// const isMac = process.platform === \"darwin\";\n\nexport const FeedController: React.FC<FeedControllerProps> = ({}) => {\n  const [cursors, setCursors] = useState([0]);\n  const { conn } = useContext(WebSocketContext);\n  const { t } = useTypeSafeTranslation();\n  const [roomModal, setRoomModal] = useState(false);\n  const { data } = useTypeSafeQuery(\"getMyScheduledRoomsAboutToStart\", {\n    enabled: !!conn,\n    refetchOnMount: \"always\",\n  });\n  const updater = useTypeSafeUpdateQuery();\n  const screenType = useScreenType();\n  const { currentRoomId } = useCurrentRoomIdStore();\n\n  let mb = \"mb-7\";\n  if (screenType === \"fullscreen\") {\n    if (currentRoomId) {\n      mb = \"mb-15\";\n    } else {\n      mb = \"mb-8\";\n    }\n  }\n  // useEffect(() => {\n  //   if (isElectron() && isMac) {\n  //     modalAlert(t(\"common.requestPermissions\"));\n  //   }\n  // }, [t]);\n\n  if (!conn) {\n    return null;\n  }\n\n  return (\n    <MiddlePanel\n      stickyChildren={\n        <FeedHeader\n          actionTitle={t(\"pages.home.createRoom\")}\n          onActionClicked={() => {\n            setRoomModal(true);\n          }}\n          title={t(\"modules.feed.yourFeed\")}\n        />\n      }\n    >\n      <div className={`flex flex-1 flex-col ${mb}`} data-testid=\"feed\">\n        <div className=\"flex flex-col space-y-4\">\n          {data?.scheduledRooms?.map((sr) => (\n            <EditScheduleRoomModalController\n              key={sr.id}\n              onScheduledRoom={(_, editedRoomData) => {\n                updater(\"getMyScheduledRoomsAboutToStart\", (x) => {\n                  return !x\n                    ? x\n                    : {\n                        scheduledRooms: x.scheduledRooms.map((y) =>\n                          y.id === sr.id\n                            ? {\n                                ...sr,\n                                name: editedRoomData.name,\n                                description: editedRoomData.description,\n                                scheduledFor: editedRoomData.scheduledFor.toISOString(),\n                              }\n                            : y\n                        ),\n                      };\n                });\n              }}\n            >\n              {({ onEdit }) => (\n                <ScheduledRoomCard\n                  info={sr}\n                  onDeleteComplete={() =>\n                    updater(\"getMyScheduledRoomsAboutToStart\", (x) =>\n                      !x\n                        ? x\n                        : {\n                            scheduledRooms: x.scheduledRooms.filter(\n                              (y) => y.id !== sr.id\n                            ),\n                          }\n                    )\n                  }\n                  onEdit={() => onEdit({ cursor: \"\", scheduleRoomToEdit: sr })}\n                  noCopyLinkButton\n                />\n              )}\n            </EditScheduleRoomModalController>\n          ))}\n          {cursors.map((cursor, i) => (\n            <Page\n              key={cursor}\n              cursor={cursor}\n              isOnlyPage={cursors.length === 1}\n              onLoadMore={(c) => setCursors([...cursors, c])}\n              isLastPage={i === cursors.length - 1}\n            />\n          ))}\n        </div>\n      </div>\n      {roomModal && (\n        <CreateRoomModal onRequestClose={() => setRoomModal(false)} />\n      )}\n    </MiddlePanel>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/dashboard/FollowingOnlineController.tsx",
    "content": "import React, { useState } from \"react\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport {\n  FollowerOnline,\n  FollowersOnlineShowMore,\n  FollowersOnlineWrapper,\n} from \"../../ui/FollowersOnline\";\nimport { InfoText } from \"../../ui/InfoText\";\n\ninterface FriendsOnlineControllerProps {}\n\nconst Page: React.FC<{\n  cursor: number;\n  onLoadMore: (cursor: number) => void;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n}> = ({ cursor, isLastPage, isOnlyPage, onLoadMore }) => {\n  const { t } = useTypeSafeTranslation();\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"getMyFollowing\", cursor],\n    {\n      refetchOnMount: \"always\",\n    },\n    [cursor]\n  );\n\n  if (isOnlyPage && !isLoading && !data?.users.length) {\n    return <InfoText>{t(\"components.followingOnline.noOnline\")}</InfoText>;\n  }\n\n  return (\n    <>\n      {data?.users.map((u) => (\n        <FollowerOnline {...u} key={u.id} />\n      ))}\n      {isLastPage && data?.nextCursor ? (\n        <FollowersOnlineShowMore\n          onClick={() => onLoadMore(data!.nextCursor!)}\n        />\n      ) : null}\n    </>\n  );\n};\n\nexport const FollowingOnlineController: React.FC<FriendsOnlineControllerProps> = ({}) => {\n  const [cursors, setCursors] = useState<number[]>([0]);\n  const conn = useConn();\n\n  if (!conn) {\n    return null;\n  }\n\n  return (\n    <FollowersOnlineWrapper>\n      {cursors.map((c, i) => (\n        <Page\n          key={c}\n          cursor={c}\n          onLoadMore={(nc) => setCursors([...cursors, nc])}\n          isLastPage={i === cursors.length - 1}\n          isOnlyPage={cursors.length === 1}\n        />\n      ))}\n    </FollowersOnlineWrapper>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/dashboard/MinimizedRoomCardController.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React from \"react\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useCurrentRoomFromCache } from \"../../shared-hooks/useCurrentRoomFromCache\";\nimport { useCurrentRoomInfo } from \"../../shared-hooks/useCurrentRoomInfo\";\nimport { useLeaveRoom } from \"../../shared-hooks/useLeaveRoom\";\nimport { useSetDeaf } from \"../../shared-hooks/useSetDeaf\";\nimport { useSetMute } from \"../../shared-hooks/useSetMute\";\nimport { MinimizedRoomCard } from \"../../ui/MinimizedRoomCard\";\n\nexport const MinimizedRoomCardController: React.FC = ({}) => {\n  const data = useCurrentRoomFromCache();\n  const { canSpeak } = useCurrentRoomInfo();\n  const { leaveRoom, isLoading } = useLeaveRoom();\n  const { muted } = useMuteStore();\n  const { deafened } = useDeafStore();\n  const setMute = useSetMute();\n  const setDeaf = useSetDeaf();\n  const router = useRouter();\n\n  if (!data || \"error\" in data) {\n    return null;\n  }\n\n  const { room } = data;\n  const dt = new Date(room.inserted_at);\n\n  return (\n    <MinimizedRoomCard\n      onFullscreenClick={() => router.push(`/room/${room.id}`)}\n      leaveLoading={isLoading}\n      room={{\n        name: room.name,\n        speakers: room.peoplePreviewList.slice(0, 3).map((s) => s.displayName),\n        roomStartedAt: dt,\n        myself: {\n          isDeafened: deafened,\n          isSpeaker: canSpeak,\n          isMuted: muted,\n          leave: () => {\n            leaveRoom();\n          },\n          switchDeafened: () => {\n            setDeaf(!deafened);\n          },\n          switchMuted: () => {\n            setMute(!muted);\n          },\n        },\n      }}\n    />\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/dashboard/ProfileBlockController.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React, { useEffect, useState } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { ContributorBadge, StaffBadge } from \"../../icons/badges\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport useWindowSize from \"../../shared-hooks/useWindowSize\";\nimport { ProfileBlock } from \"../../ui/ProfileBlock\";\nimport { UpcomingRoomsCard } from \"../../ui/UpcomingRoomsCard\";\nimport { badge, UserSummaryCard } from \"../../ui/UserSummaryCard\";\nimport { CreateScheduleRoomModal } from \"../scheduled-rooms/CreateScheduledRoomModal\";\nimport { EditProfileModal } from \"../user/EditProfileModal\";\nimport { MinimizedRoomCardController } from \"./MinimizedRoomCardController\";\n\ninterface ProfileBlockControllerProps {}\n\nexport const ProfileBlockController: React.FC<ProfileBlockControllerProps> = ({}) => {\n  const [upcomingCount, setUpcomingCount] = useState(3);\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const conn = useConn();\n  const [showEditProfileModal, setShowEditProfileModal] = useState(false);\n  const [\n    showCreateScheduleRoomModal,\n    setShowCreateScheduleRoomModal,\n  ] = useState(false);\n  const { data } = useTypeSafeQuery([\"getScheduledRooms\", \"\"]);\n  const { push } = useRouter();\n  const update = useTypeSafeUpdateQuery();\n  const { height } = useWindowSize();\n  const { t } = useTypeSafeTranslation();\n\n  const badges: badge[] = [];\n  if (conn.user.staff) {\n    badges.push({\n      content: <StaffBadge />,\n      variant: \"primary\",\n      color: \"white\",\n      title: t(\"components.userBadges.dhStaff\"),\n      naked: true,\n    });\n  }\n\n  if (conn.user.contributions > 0) {\n    badges.push({\n      content: <ContributorBadge contributions={conn.user.contributions} />,\n      variant: \"primary\",\n      color: \"white\",\n      title: `${t(\"components.userBadges.dhContributor\")} (${conn.user.contributions} ${t(\"pages.admin.contributions\")})`,\n      naked: true,\n    });\n  }\n\n  if (conn.user.botOwnerId) {\n    badges.push({\n      content: t(\"pages.viewUser.bot\"),\n      variant: \"primary\",\n      color: \"white\",\n      title: t(\"pages.viewUser.bot\"),\n    });\n  }\n\n  useEffect(() => {\n    if (height && height < 780) {\n      setUpcomingCount(2);\n    } else {\n      setUpcomingCount(3);\n    }\n  }, [height]);\n\n  return (\n    <>\n      <EditProfileModal\n        isOpen={showEditProfileModal}\n        onRequestClose={() => setShowEditProfileModal(false)}\n      />\n      {showCreateScheduleRoomModal ? (\n        <CreateScheduleRoomModal\n          onScheduledRoom={(srData, resp) => {\n            update([\"getScheduledRooms\", \"\"], (d) => {\n              return {\n                rooms: [\n                  {\n                    roomId: null,\n                    creator: conn.user!,\n                    creatorId: conn.user!.id,\n                    description: srData.description,\n                    id: resp.scheduledRoom.id,\n                    name: srData.name,\n                    numAttending: 0,\n                    scheduledFor: srData.scheduledFor.toISOString(),\n                  },\n                  ...(d?.rooms || []),\n                ],\n                nextCursor: d?.nextCursor,\n              };\n            });\n          }}\n          onRequestClose={() => setShowCreateScheduleRoomModal(false)}\n        />\n      ) : null}\n      <ProfileBlock\n        top={\n          currentRoomId ? (\n            <MinimizedRoomCardController />\n          ) : (\n            <UserSummaryCard\n              onClick={() => setShowEditProfileModal(true)}\n              badges={badges}\n              website=\"\"\n              isOnline={false}\n              {...conn.user}\n              username={conn.user.username}\n            />\n          )\n        }\n        bottom={\n          <UpcomingRoomsCard\n            onCreateScheduledRoom={() => setShowCreateScheduleRoomModal(true)}\n            rooms={\n              data?.rooms.slice(0, upcomingCount).map((sr) => ({\n                onClick: () => {\n                  push(`/scheduled-room/[id]`, `/scheduled-room/${sr.id}`);\n                },\n                id: sr.id,\n                scheduledFor: new Date(sr.scheduledFor),\n                title: sr.name,\n                speakersInfo: {\n                  avatars: [sr.creator.avatarUrl],\n                  speakers: [sr.creator.username],\n                },\n              })) || []\n            }\n          />\n        }\n      />\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/debugging/AudioDebugAvatar.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport hark from \"hark\";\nimport { useConsumerStore } from \"../webrtc/stores/useConsumerStore\";\n\ninterface AudioDebugAvatarProps {\n  id: string;\n}\n\nconst colorMap = {\n  \"no-consumer\": \"red\",\n  closed: \"orange\",\n  good: \"green\",\n};\n\nexport const AudioDebugAvatar: React.FC<AudioDebugAvatarProps> = ({\n  id,\n  children,\n}) => {\n  const [n, setN] = useState(0);\n  const { consumerMap } = useConsumerStore();\n  const consumer = consumerMap[id];\n  let state = \"\";\n  if (!consumer) {\n    state = \"no-consumer\";\n  } else if (consumer.consumer.closed) {\n    state = \"closed\";\n  } else {\n    state = \"good\";\n  }\n\n  useEffect(() => {\n    if (!consumer) {\n      return;\n    }\n    const harker = hark(new MediaStream([consumer.consumer.track]));\n    harker.setInterval(500);\n\n    harker.on(\"volume_change\", (x) => {\n      const min = -100;\n      const max = 100;\n\n      // normalize between 0 and 1\n      setN((x - min) / (max - min));\n    });\n\n    return () => {\n      harker.stop();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [consumer?.consumer.id]);\n\n  return (\n    <div style={{ height: 60 }} className=\"relative\">\n      {children}\n      <div className=\"rounded-full flex absolute top-0 h-full w-full opacity-70 overflow-hidden\">\n        <div\n          className=\"mt-auto w-full\"\n          style={{\n            height: 60 * n,\n            backgroundColor: colorMap[state as keyof typeof colorMap] || \"\",\n          }}\n        />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/debugging/AudioDebugPanel.tsx",
    "content": "import { detectDevice } from \"mediasoup-client\";\nimport React, { useEffect, useState } from \"react\";\nimport { useVoiceStore } from \"../webrtc/stores/useVoiceStore\";\n\ninterface AudioDebugPanelProps {}\n\nexport const AudioDebugPanel: React.FC<AudioDebugPanelProps> = ({}) => {\n  const [, rerender] = useState(0);\n  const { recvTransport } = useVoiceStore();\n\n  useEffect(() => {\n    if (!recvTransport) {\n      return;\n    }\n\n    const listener = () => {\n      rerender((c) => c + 1);\n    };\n\n    recvTransport.on(\"connectionstatechange\", listener);\n\n    return () => {\n      recvTransport.removeListener(\"connectionstatechange\", listener);\n    };\n  }, [recvTransport]);\n\n  const [supportedDevice] = useState(() => !!detectDevice());\n\n  return (\n    <div className=\"text-primary-100 bg-primary-600 p-1 mb-2\">\n      <h4>Audio Debug Information</h4>\n      <div>\n        recv transport state:{\" \"}\n        <span className=\"text-accent\">{recvTransport?.connectionState}</span>\n      </div>\n      {!supportedDevice ? (\n        <div className=\"mt-2\">\n          WARNING: Your browser is not officially supported and has defaulted to\n          Chrome74 audio driver which may affect your experience.{\" \"}\n          <a\n            className=\"text-accent\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n            href=\"https://mediasoup.org/documentation/v3/mediasoup-client/api/#BuiltinHandlerName\"\n          >\n            List of supported browsers.\n          </a>\n        </div>\n      ) : null}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/Bot.tsx",
    "content": "export interface Bot {\n  id: string;\n  username: string;\n  avatarUrl: string;\n  displayName: string;\n  apiKey: string;\n}\n"
  },
  {
    "path": "kibbeh/src/modules/developer/BotCard.tsx",
    "content": "import React from \"react\";\nimport { useRouter } from \"next/router\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { SingleUser } from \"../../ui/UserAvatar\";\nimport { Bot } from \"./Bot\";\n\ninterface BotCardProps {\n  bot: Bot\n}\n\nexport const BotCard: React.FC<BotCardProps> = ({ bot }) => {\n  const { t } = useTypeSafeTranslation();\n  const { push } = useRouter();\n\n  return (\n    <button\n    className=\"flex flex-col bg-primary-800 cursor-pointer rounded-lg items-center justify-center\"\n    style={{ width: 140, height: 140 }}\n    onClick={() => push(`/developer/bots/edit/[username]`, `/developer/bots/edit/${bot.username}`)}\n    >\n        <div>\n            <SingleUser isOnline={true} src={bot.avatarUrl} username={bot.username}></SingleUser>\n        </div>\n        <div className=\"font-bold text-base\">\n            {bot.displayName}\n        </div>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/BotIcon.tsx",
    "content": "import React, { useState } from \"react\";\n\ninterface BotIconProps {\n  username?: string;\n  src: string;\n  onClick: () => any;\n}\n\nexport const BotIcon: React.FC<BotIconProps> = ({ username, src, onClick }) => {\n  const [isError, setError] = useState(false);\n  return (\n    <div\n      className=\"mb-2 grid\"\n      style={{ width: 120, height: 120 }}\n      data-testid=\"single-user-avatar\"\n    >\n      <img\n        alt={`${username ?? ''}-s-avatar`}\n        className={\"rounded-full w-full h-full object-cover relative\"}\n        style={{ gridColumn: 1, gridRow: 1 }}\n        onError={() => setError(true)}\n        src={\n          isError\n            ? `https://ui-avatars.com/api/${\n                username ? `&name=${username}` : \"&name\"\n              }&rounded=true&background=B23439&bold=true&color=FFFFFF`\n            : src\n        }\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/BotInfo.tsx",
    "content": "import React from \"react\";\nimport { Bot } from \"./Bot\";\nimport { BotIcon } from \"./BotIcon\";\n\ninterface BotInfoProps {\n  bot: Bot;\n  onClick: () => any;\n}\n\nexport const BotInfo: React.FC<BotInfoProps> = ({ bot, onClick }) => {\n  return (\n    <div\n      className=\"flex flex-col bg-primary-800 rounded-lg items-center justify-center\"\n      style={{ width: 190, height: 200 }}\n    >\n      <BotIcon\n        username={bot.username}\n        src={bot.avatarUrl}\n        onClick={onClick}\n      />\n      <div className=\"flex text-base font-medium\">{bot.displayName}</div>\n      <div className=\"flex text-primary-300 text-base font-medium\">\n        @{bot.username}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/BotsEditPage.tsx",
    "content": "import React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { MainLayout } from \"../layouts/MainLayout\";\nimport { FloatingRoomInfo } from \"../layouts/FloatingRoomInfo\";\nimport { TabletSidebar } from \"../layouts/TabletSidebar\";\nimport { DeveloperPanel } from \"./DeveloperPanel\";\nimport { ProfileBlockController } from \"../dashboard/ProfileBlockController\";\nimport { EditBot } from \"./EditBot\";\n\ninterface BotsEditPageProps {}\n\nexport const BotsEditPage: PageComponent<BotsEditPageProps> = ({}) => {\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title={\"Edit Bot\"} />\n      <MainLayout\n        floatingRoomInfo={<FloatingRoomInfo />}\n        tabletSidebar={<TabletSidebar />}\n        leftPanel={<DeveloperPanel />}\n        rightPanel={<ProfileBlockController />}\n        mobileHeader={undefined}\n      >\n        <EditBot />\n      </MainLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nBotsEditPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/developer/BotsPage.tsx",
    "content": "import React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { MainLayout } from \"../layouts/MainLayout\";\nimport { FloatingRoomInfo } from \"../layouts/FloatingRoomInfo\";\nimport { TabletSidebar } from \"../layouts/TabletSidebar\";\nimport { DeveloperPanel } from \"./DeveloperPanel\";\nimport { ProfileBlockController } from \"../dashboard/ProfileBlockController\";\nimport { YourBots } from \"./YourBots\";\n\nexport const BotsPage: PageComponent<unknown> = ({}) => {\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title={\"Bots\"} />\n      <MainLayout\n        floatingRoomInfo={<FloatingRoomInfo />}\n        tabletSidebar={<TabletSidebar />}\n        leftPanel={<DeveloperPanel />}\n        rightPanel={<ProfileBlockController />}\n        mobileHeader={undefined}\n      >\n        <YourBots />\n      </MainLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nBotsPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/developer/CreateBotModal.tsx",
    "content": "import { Form, Formik } from \"formik\";\nimport React from \"react\";\nimport { InputField } from \"../../form-fields/InputField\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { ButtonLink } from \"../../ui/ButtonLink\";\nimport { Modal } from \"../../ui/Modal\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\ninterface CreateBotModalProps {\n  onRequestClose: () => void;\n  data?: {\n    username: string;\n  };\n}\n\nexport const CreateBotModal: React.FC<CreateBotModalProps> = ({\n  onRequestClose,\n  data,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const wrapper = useWrappedConn();\n\n  return (\n    <Modal isOpen onRequestClose={onRequestClose}>\n      <Formik<{\n        username: string;\n      }>\n        initialValues={\n          data\n            ? data\n            : {\n                username: \"\",\n              }\n        }\n        validateOnChange={false}\n        validateOnBlur={false}\n        onSubmit={({ username }) => {\n          wrapper.mutation.userCreateBot(username).then((r) => {\n            if (r.isUsernameTaken) {\n              showErrorToast(\n                t(\"components.modals.createBotModal.usernameTaken\")\n              );\n            }\n            if (r.error) {\n              showErrorToast(r.error);\n            }\n          });\n          onRequestClose();\n        }}\n      >\n        {({ isSubmitting }) => (\n          <Form className={`grid grid-cols-3 gap-4 focus:outline-none w-full`}>\n            <div className={`col-span-3 block`}>\n              <h4 className={`mb-2 text-primary-100`}>\n                {t(\"components.modals.createBotModal.title\")}\n              </h4>\n              <div className={`text-primary-300`}>\n                {t(\"components.modals.createBotModal.subtitle\")}\n              </div>\n            </div>\n            <div className={`flex h-full w-full col-span-2`}>\n              <InputField\n                className={`mb-4`}\n                errorMsg={t(\"components.modals.editProfileModal.usernameError\")}\n                label={t(\"components.modals.editProfileModal.usernameLabel\")}\n                name=\"username\"\n              />\n            </div>\n\n            <div className={`flex pt-2 space-x-3 col-span-full items-center`}>\n              <Button loading={isSubmitting} type=\"submit\" className={`mr-3`}>\n                {t(\"components.modals.createBotModal.title\")}\n              </Button>\n              <ButtonLink type=\"button\" onClick={onRequestClose}>\n                {t(\"common.cancel\")}\n              </ButtonLink>\n            </div>\n          </Form>\n        )}\n      </Formik>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/DeveloperNavButton.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\nexport const DeveloperNavButton: React.FC<{ title: string, icon: React.ReactNode, href: string }> = ({ title, icon, href }) => {\n  const { t } = useTypeSafeTranslation();\n  const { push } = useRouter();\n\n  return (\n    <button\n      className=\"flex text-primary-100 bg-primary-800 rounded-lg text-base font-bold items-center cursor-pointer md:hover:bg-primary-700\"\n      style={{\n        width: 200,\n        height: 40\n      }}\n      onClick={() => push(href, href)}\n    >\n      <div className=\"mr-3 ml-4\">\n        {icon}\n      </div>\n      <div>\n        {title}\n      </div>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/DeveloperPanel.tsx",
    "content": "import { BotIcon } from \"../../icons\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { DeveloperNavButton } from \"./DeveloperNavButton\";\n\nexport const DeveloperPanel: React.FC<unknown> = ({}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <div className=\"w-full flex flex-col flex-1\" data-testid=\"developer-pages\">\n      <h4 className=\"text-primary-100\">\n        {t(\"components.settingsDropdown.developer\")}\n      </h4>\n      <div className=\"flex flex-col mt-3 gap-3\">\n        <DeveloperNavButton\n          title={t(\"pages.botEdit.bots\")}\n          href=\"/developer/bots\"\n          icon={<BotIcon width={16} height={16} />}\n        />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/developer/EditBot.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { Bot } from \"./Bot\";\nimport { BotInfo } from \"./BotInfo\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { Button } from \"../../ui/Button\";\nimport { useRouter } from \"next/router\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\nexport const EditBot = ({}) => {\n  const { query } = useRouter();\n  const username = query.username;\n  const [showapiKey, setShowapiKey] = useState(false);\n  const [bots, setBots] = useState<Array<Bot>>([]);\n  const [bot, setBot] = useState<Bot>();\n  const wrapper = useWrappedConn();\n  const { t } = useTypeSafeTranslation();\n  useEffect(() => {\n    wrapper.connection.sendCall(\"user:get_bots\", {}).then((v: any) => {\n      setBots(v.bots);\n    });\n  }, []);\n\n  useEffect(() => {\n    setBot(bots.find((b) => b.username === username));\n  }, [bots]);\n\n  if (!bot) {\n    return <div>{t(\"common.error\")}</div>;\n  }\n  return (\n    <div\n      className=\"flex flex-col text-primary-100\"\n      style={{ marginTop: 130, paddingLeft: 20, paddingRight: 20 }}\n    >\n      <div className=\"flex flex-row w-full justify-between mb-4\">\n        <div className=\"flex text-2xl font-bold\">\n          {t(\"pages.botEdit.title\")}\n        </div>\n      </div>\n      <div className=\"flex flex-row w-full justify-between\">\n        <BotInfo bot={bot} onClick={() => {}} />\n\n        <div className=\"flex flex-col justify-between\" style={{ height: 200 }}>\n          <div\n            className=\"flex flex-col justify-between bg-primary-800 rounded-lg p-4\"\n            style={{ width: 390, height: 150 }}\n          >\n            <div className=\"flex flex-col\">\n              <div className=\"text-base font-bold\">\n                {t(\"pages.botEdit.apiKey\")}\n              </div>\n              <div\n                className={`flex items-center justify-start bg-primary-900 w-full rounded pl-2 ${\n                  !showapiKey ? \"text-accent cursor-pointer\" : \"\"\n                }`}\n                style={{ height: 25 }}\n                onClick={() => setShowapiKey(true)}\n              >\n                {showapiKey ? bot.apiKey : t(\"pages.botEdit.reveal\")}\n              </div>\n            </div>\n            <div className=\"flex flex-row justify-between\">\n              <Button\n                color=\"secondary\"\n                onClick={() => navigator.clipboard?.writeText(bot.apiKey)}\n              >\n                {t(\"common.copy\")}\n              </Button>\n              <Button\n                color=\"secondary\"\n                onClick={() => {\n                  wrapper.connection\n                    .sendCall(\"user:revoke_api_key\", {\n                      userId: bot.id,\n                    })\n                    .then((r: any) => {\n                      setBot({\n                        id: r.id,\n                        apiKey: r.apiKey,\n                        username: bot.username,\n                        displayName: bot.displayName,\n                        avatarUrl: bot.avatarUrl,\n                      });\n                    });\n                }}\n              >\n                {t(\"pages.botEdit.regenerate\")}\n              </Button>\n            </div>\n          </div>\n\n          <Button disabled onClick={() => null} title=\"Coming Soon™\">\n            {t(\"common.delete\")}\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nEditBot.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/developer/YourBots.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { BotCard } from \"./BotCard\";\nimport { Bot } from \"./Bot\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { CreateBotModal } from \"./CreateBotModal\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\nexport const YourBots: React.FC<unknown> = ({}) => {\n  const [bots, setBots] = useState<Array<Bot>>([]);\n  const [modal, setModal] = useState(false);\n  const botsParsed = bots.map((v, i) => (\n    <BotCard key={v.displayName + v.avatarUrl + i} bot={v} />\n  ));\n  const wrapper = useWrappedConn();\n  const { t } = useTypeSafeTranslation();\n  // wrapper.wrapperection.sendCall('user:create_bot', {username: 'ttttt'}).then(v => console.log(v));\n  useEffect(() => {\n    wrapper.connection.sendCall(\"user:get_bots\", {}).then((v: any) => {\n      setBots(v.bots);\n    });\n  }, [modal]);\n  return (\n    <div\n      className=\"flex flex-col text-primary-100 text-2xl font-bold\"\n      style={{ marginTop: 130, paddingLeft: 20, paddingRight: 20 }}\n    >\n      <div className=\"flex flex-row w-full justify-between\">\n        <div className=\"flex\">\n          {t('pages.botEdit.yourBots')} ({bots.length})\n        </div>\n        {bots.length < 5 ? (\n          <button\n            className=\"flex bg-accent md:hover:bg-accent-hover cursor-pointer rounded-lg text-base font-bold content-center justify-center\"\n            style={{\n              width: 120,\n              height: 30,\n              lineHeight: \"30px\",\n              textAlign: \"center\",\n            }}\n            onClick={() => {\n              setModal(true);\n            }}\n          >\n            Create bot\n          </button>\n        ) : (\n          <div className=\"flex text-accent\">Max amount of bots reached!</div>\n        )}\n      </div>\n      <div\n        className=\"inline-block w-full bg-primary-300\"\n        style={{ height: 1, marginTop: '0.75rem', marginBottom: '0.75rem' }}\n      ></div>\n      <div\n        className=\"flex flex-wrap justify-start\"\n        style={{ columnGap: \"calc(calc(100% - 560px) / 3)\", rowGap: \"1.5rem\" }}\n      >\n        {botsParsed}\n      </div>\n\n      {modal && <CreateBotModal onRequestClose={() => setModal(false)} />}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/display/HeaderController.tsx",
    "content": "import React from \"react\";\nimport Header from \"next/head\";\nimport { NextPage } from \"next\";\nimport { baseUrl } from \"../../lib/constants\";\nexport interface HeaderControllerProps {\n  title?: string;\n  embed?: { hexColor?: string; image?: string };\n  owner?: string;\n  additionalKeywords?: string[];\n  description?: string;\n}\n\nexport const HeaderController: NextPage<HeaderControllerProps> = ({\n  title,\n  description = \"Dogehouse is taking voice conversations to the moon 🚀\",\n  owner,\n  additionalKeywords = [],\n  embed,\n}) => {\n  return (\n    <Header>\n      {title ? <title>{title} | DogeHouse</title> : <title>DogeHouse</title>}\n      <meta name=\"description\" content={description} />\n      {owner ? <meta name=\"author\" content={owner} /> : \"\"}\n      <meta\n        name=\"keywords\"\n        content={`DogeHouse, Doge${additionalKeywords?.map((k) => `, ${k}`)}`}\n      />\n      <meta name=\"theme-color\" content={embed?.hexColor || \"#EFE7DD\"} />\n      {embed ? (\n        <>\n          <meta name=\"og:title\" content={title || \"DogeHouse\"} />\n          <meta\n            name=\"og:type\"\n            content={owner ? \"music.radio_station\" : \"website\"}\n          />\n          {owner ? <meta name=\"music:creator\" content={owner} /> : \"\"}\n          <meta name=\"og:description\" content={description} />\n          <meta name=\"og:site_name\" content=\"DogeHouse\" />\n          <meta name=\"og:image\" content={embed.image ? embed.image : `${baseUrl}/img/doge.png`} />\n        </>\n      ) : (\n        \"\"\n      )}\n    </Header>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/display/TextParser.tsx",
    "content": "import React from 'react';\nimport { linkRegex } from '../../lib/constants';\nimport emojiRegex from \"emoji-regex\";\nimport { ParseTextToTwemoji } from '../../ui/Twemoji';\n\ninterface TextParserProps {\n  children: string\n}\n\nexport const TextParser: React.FC<TextParserProps> = ({ children }) => {\n  return (<>\n    {children.split(/(?=[ ,\\n])|(?<=[ ,\\n])/g).map((text, i) => {\n      if (new RegExp(linkRegex).test(text)) return <a key={i} className={\"text-accent text-center hover:underline inline font-bold\"} href={text}>{text}</a>;\n      if (emojiRegex().test(text)) return <ParseTextToTwemoji key={i} text={text}/>;\n      return text;\n    })}\n  </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/errors/ErrorToastController.tsx",
    "content": "import React from \"react\";\nimport { MainInnerGrid } from \"../../ui/MainGrid\";\nimport { ErrorToast } from \"../../ui/ErrorToast\";\nimport { useErrorToastStore } from \"./useErrorToastStore\";\n\ninterface ErrorToastControllerProps {}\n\nexport const ErrorToastController: React.FC<ErrorToastControllerProps> = ({}) => {\n  const { toasts, hideToast } = useErrorToastStore();\n  return (\n    <div\n      style={{ zIndex: 1001 }}\n      className={`flex w-full fixed bottom-0 justify-center`}\n    >\n      <MainInnerGrid>\n        <div />\n        <div className={`flex flex-col w-full`}>\n          {toasts.map((t) => (\n            <div key={t.id} className={`flex mb-3`}>\n              <ErrorToast\n                message={t.message}\n                duration={t.duration}\n                onClose={() => hideToast(t.id)}\n              />\n            </div>\n          ))}\n        </div>\n        <div />\n      </MainInnerGrid>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/errors/useErrorToastStore.tsx",
    "content": "import React from \"react\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { v4 } from \"uuid\";\nimport { ToastDurations } from \"../../ui/ErrorToast\";\n\ntype Toast = {\n  id: string;\n  button?: React.ReactNode;\n  duration?: ToastDurations;\n  message: string;\n};\n\nexport const useErrorToastStore = create(\n  combine(\n    {\n      toasts: [] as Toast[],\n    },\n    (set) => ({\n      hideToast: (id: string) =>\n        set((x) => ({ toasts: x.toasts.filter((y) => y.id !== id) })),\n      showToast: (t: Omit<Toast, \"id\">) =>\n        set((x) => {\n          const currentRemovedToasts: Toast[] = x.toasts.filter((y) => y.message !== t.message);\n          return {\n            toasts: [...currentRemovedToasts, { ...t, id: v4() }]\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/ChatKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface ChatKeybindProps {\n  className?: string;\n}\n\nexport const ChatKeybind: React.FC<ChatKeybindProps> = ({ className }) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { CHAT },\n    setChatKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setChatKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setChatKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        toggle chat keybind:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? \"listening\" : CHAT}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/DeafKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface DeafKeybindProps {\n  className?: string;\n}\n\nexport const DeafKeybind: React.FC<DeafKeybindProps> = ({ className }) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { DEAF },\n    setDeafKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setDeafKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setDeafKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        {t(\"components.keyboardShortcuts.toggleDeafKeybind\")}:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? t(\"components.keyboardShortcuts.listening\") : DEAF}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/InviteKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface InviteKeybindProps {\n  className?: string;\n}\n\nexport const InviteKeybind: React.FC<InviteKeybindProps> = ({ className }) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { INVITE },\n    setInviteKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setInviteKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setInviteKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        invite keybind:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? \"listening\" : INVITE}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/KeybindListener.tsx",
    "content": "import { wrap, Wrapper } from \"@dogehouse/kebab\";\nimport isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport React, { useContext, useEffect, useMemo } from \"react\";\nimport { GlobalHotKeys } from \"react-hotkeys\";\nimport {\n  CHAT_KEY,\n  INVITE_KEY,\n  MUTE_KEY,\n  DEAF_KEY,\n  REQUEST_TO_SPEAK_KEY,\n  useKeyMapStore,\n} from \"../../global-stores/useKeyMapStore\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { modalConfirm } from \"../../shared-components/ConfirmModal\";\nimport { setMute } from \"../../shared-hooks/useSetMute\";\nimport { setDeaf } from \"../../shared-hooks/useSetDeaf\";\nimport { useRoomChatStore } from \"../room/chat/useRoomChatStore\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\n\nlet ipcRenderer: any = undefined;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\n\ninterface KeybindListenerProps {}\n\nfunction ListenerElectron() {\n  const { push } = useRouter();\n  const { conn } = useContext(WebSocketContext);\n  const toggleOpen = useRoomChatStore((s) => s.toggleOpen);\n\n  useEffect(() => {\n    if (!conn) {\n      return {} as any;\n    }\n    const wrapper: Wrapper = wrap(conn);\n    // keybind event functions\n    const REQUEST_TO_SPEAK_KEY_FUNC = (event: any, args: any) => {\n      modalConfirm(\"Would you like to ask to speak?\", () => {\n        wrapper.mutation.askToSpeak();\n      });\n    };\n    const MUTE_KEY_FUNC = (event: any, args: any) => {\n      const { muted } = useMuteStore.getState();\n      setMute(wrapper, !muted);\n    };\n    const DEAF_KEY_FUNC = (event: any, args: any) => {\n      const { deafened } = useDeafStore.getState();\n      setDeaf(wrapper, !deafened);\n    };\n    const INVITE_KEY_FUNC = (event: any, args: any) => {\n      push(\"/invite\");\n    };\n    const PTT_STATUS_CHANGE_FUNC = (event: any, status: boolean) => {\n      if (!event) return;\n      const mute = status;\n      setMute(wrapper, mute);\n    };\n    const CHAT_KEY_FUNC = (event: any, args: any) => {\n      toggleOpen();\n    };\n\n    // Subscribing to keybind events\n    ipcRenderer.on(REQUEST_TO_SPEAK_KEY, REQUEST_TO_SPEAK_KEY_FUNC);\n    ipcRenderer.on(MUTE_KEY, MUTE_KEY_FUNC);\n    ipcRenderer.on(DEAF_KEY, DEAF_KEY_FUNC);\n    ipcRenderer.on(INVITE_KEY, INVITE_KEY_FUNC);\n    ipcRenderer.on(\"@voice/ptt_status_change\", PTT_STATUS_CHANGE_FUNC);\n    ipcRenderer.on(CHAT_KEY, CHAT_KEY_FUNC);\n\n    return function cleanup() {\n      // bUnsubscribing from keybind events\n      ipcRenderer.removeListener(\n        REQUEST_TO_SPEAK_KEY,\n        REQUEST_TO_SPEAK_KEY_FUNC\n      );\n      ipcRenderer.removeListener(MUTE_KEY, MUTE_KEY_FUNC);\n      ipcRenderer.removeListener(DEAF_KEY, DEAF_KEY_FUNC);\n      ipcRenderer.removeListener(INVITE_KEY, INVITE_KEY_FUNC);\n      ipcRenderer.removeListener(\n        \"@voice/ptt_status_change\",\n        PTT_STATUS_CHANGE_FUNC\n      );\n      ipcRenderer.removeListener(CHAT_KEY, CHAT_KEY_FUNC);\n    };\n  }, [conn, push, toggleOpen]);\n\n  return null;\n}\n\nfunction ListenerBrowser() {\n  const { push } = useRouter();\n  const { conn } = useContext(WebSocketContext);\n  const { keyMap } = useKeyMapStore();\n  const toggleOpen = useRoomChatStore((s) => s.toggleOpen);\n  return (\n    <GlobalHotKeys\n      allowChanges={true}\n      keyMap={keyMap}\n      handlers={useMemo(() => {\n        if (!conn) {\n          return {} as any;\n        }\n        const wrapper = wrap(conn);\n        return {\n          REQUEST_TO_SPEAK: () => {\n            modalConfirm(\"Would you like to ask to speak?\", () => {\n              wrapper.mutation.askToSpeak();\n            });\n          },\n          MUTE: () => {\n            const { muted } = useMuteStore.getState();\n            setMute(wrapper, !muted);\n          },\n          DEAF: () => {\n            const { deafened } = useDeafStore.getState();\n            setDeaf(wrapper, !deafened);\n          },\n          INVITE: () => {\n            // wsend({ op: \"fetch_invite_list\", d: { cursor: 0 } });\n            // @todo\n            push(\"/invite\");\n          },\n          PTT: (e) => {\n            if (!e) return;\n            const mute = e.type === \"keyup\";\n            setMute(wrapper, mute);\n          },\n          CHAT: toggleOpen,\n        };\n      }, [push, toggleOpen, conn])}\n    />\n  );\n}\n\nexport const KeybindListener: React.FC<KeybindListenerProps> = ({}) => {\n  return <>{isElectron() ? <ListenerElectron /> : <ListenerBrowser />}</>;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/MuteKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface MuteKeybindProps {\n  className?: string;\n}\n\nexport const MuteKeybind: React.FC<MuteKeybindProps> = ({ className }) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { MUTE },\n    setMuteKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setMuteKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setMuteKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        {t(\"components.keyboardShortcuts.toggleMuteKeybind\")}:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? t(\"components.keyboardShortcuts.listening\") : MUTE}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/OverlayKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface OverlayKeybindProps {\n  className?: string;\n}\n\nexport const OverlayKeybind: React.FC<OverlayKeybindProps> = ({\n  className,\n}) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { OVERLAY },\n    setOverlayKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setOverlayKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setOverlayKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        {t(\"components.keyboardShortcuts.toggleOverlayKeybind\")}:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? t(\"components.keyboardShortcuts.listening\") : OVERLAY}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/PTTKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface PTTKeybindProps {\n  className?: string;\n}\n\nexport const PTTKeybind: React.FC<PTTKeybindProps> = ({ className }) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { PTT },\n    setPTTKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setPTTKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setPTTKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        {t(\"components.keyboardShortcuts.togglePushToTalkKeybind\")}:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? t(\"components.keyboardShortcuts.listening\") : PTT}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/RequestToSpeakKeybind.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { recordKeyCombination } from \"react-hotkeys\";\nimport { useKeyMapStore } from \"../../global-stores/useKeyMapStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\n\ninterface RequestToSpeakKeybindProps {\n  className?: string;\n}\n\nexport const RequestToSpeakKeybind: React.FC<RequestToSpeakKeybindProps> = ({\n  className,\n}) => {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  const {\n    keyNames: { REQUEST_TO_SPEAK },\n    setRequestToSpeakKeybind,\n  } = useKeyMapStore();\n  useEffect(() => {\n    if (count > 0) {\n      const unsub = recordKeyCombination(({ id }) => {\n        setActive(false);\n        setRequestToSpeakKeybind(id as string);\n      });\n\n      return () => unsub();\n    }\n  }, [count, setRequestToSpeakKeybind]);\n\n  return (\n    <div className={`flex items-center ${className}`}>\n      <Button\n        size=\"small\"\n        onClick={() => {\n          setCount((c) => c + 1);\n          setActive(true);\n        }}\n      >\n        {t(\"components.keyboardShortcuts.setKeybind\")}\n      </Button>\n      <div className={`flex ml-4`}>\n        request to speak keybind:{\" \"}\n        <span className={`font-bold text-lg`}>\n          {active ? \"listening\" : REQUEST_TO_SPEAK}\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/keyboard-shortcuts/index.ts",
    "content": "export { RequestToSpeakKeybind } from \"./RequestToSpeakKeybind\";\nexport { InviteKeybind } from \"./InviteKeybind\";\nexport { MuteKeybind } from \"./MuteKeybind\";\nexport { DeafKeybind } from \"./DeafKeybind\";\nexport { ChatKeybind } from \"./ChatKeybind\";\nexport { PTTKeybind } from \"./PTTKeybind\";\nexport { OverlayKeybind } from \"./OverlayKeybind\";\n"
  },
  {
    "path": "kibbeh/src/modules/landing-page/LoginPage.tsx",
    "content": "import isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport React, { useCallback, useContext, useEffect, useState } from \"react\";\nimport { LgLogo } from \"../../icons\";\nimport SvgSolidBug from \"../../icons/SolidBug\";\nimport SvgSolidDiscord from \"../../icons/SolidDiscord\";\nimport SvgSolidGitHub from \"../../icons/SolidGitHub\";\nimport SvgSolidTwitter from \"../../icons/SolidTwitter\";\nimport {\n  apiBaseUrl,\n  isStaging,\n  loginNextPathKey,\n  __prod__,\n} from \"../../lib/constants\";\nimport { isServer } from \"../../lib/isServer\";\nimport { Button } from \"../../ui/Button\";\nimport { useSaveTokensFromQueryParams } from \"../auth/useSaveTokensFromQueryParams\";\nimport { useTokenStore } from \"../auth/useTokenStore\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { ElectronHeader } from \"../layouts/ElectronHeader\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\n\n/*\ni know this code is kinda garbage but that's because the mockup is garbage and doesn't use the design system\n */\n\ninterface LoginButtonProps {\n  children: [React.ReactNode, React.ReactNode];\n  dev?: true;\n  onClick?: () => void;\n  oauthUrl?: string; // React.FC didn't like & ({ onClick: () => void } | { oauthUrl: string }) so yeah\n}\n\nconst LoginButton: React.FC<LoginButtonProps> = ({\n  children,\n  onClick,\n  oauthUrl,\n  dev,\n  ...props\n}) => {\n  const { query } = useRouter();\n  const clickHandler = useCallback(() => {\n    if (typeof query.next === \"string\" && query.next) {\n      try {\n        localStorage.setItem(loginNextPathKey, query.next);\n      } catch {}\n    }\n\n    window.location.href = oauthUrl as string;\n  }, [query, oauthUrl]);\n\n  return (\n    <Button\n      className=\"justify-center text-base py-3 mt-2\"\n      color={dev ? \"primary\" : \"secondary\"}\n      onClick={oauthUrl ? clickHandler : onClick}\n      {...props}\n    >\n      <div\n        className=\"grid gap-4\"\n        style={{\n          gridTemplateColumns: \"1fr auto 1fr\",\n        }}\n      >\n        {children[0]}\n        {children[1]}\n        <div />\n      </div>\n    </Button>\n  );\n};\n\nexport const LoginPage: React.FC = () => {\n  useSaveTokensFromQueryParams();\n  const hasTokens = useTokenStore((s) => !!(s.accessToken && s.refreshToken));\n  const { setConn } = useContext(WebSocketContext);\n  const { push } = useRouter();\n  const [tokensChecked, setTokensChecked] = useState(false);\n\n  useEffect(() => {\n    // only want this on mount\n    setConn(null);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    if (hasTokens) {\n      push(\"/dash\");\n    } else {\n      setTokensChecked(true);\n    }\n  }, [hasTokens, push]);\n\n  const queryParams =\n    isStaging && !isServer\n      ? \"?redirect_after_base=\" + window.location.origin\n      : \"\";\n\n  if (!tokensChecked) return null;\n\n  return (\n    <>\n      <div className=\"flex\">\n        <ElectronHeader />\n      </div>\n      <div\n        className=\"grid w-full h-full\"\n        style={{\n          gridTemplateRows: \"1fr auto 1fr\",\n        }}\n      >\n        <HeaderController embed={{}} title=\"Login\" />\n        <div className=\"hidden sm:flex\" />\n        <div className=\"flex justify-self-center self-center sm:hidden\">\n          <LgLogo />\n        </div>\n        <div className=\"flex m-auto flex-col p-6 gap-5 bg-primary-800 sm:rounded-8 z-10 sm:w-400 w-full\">\n          <div className=\"flex gap-2 flex-col\">\n            <span className=\"text-3xl text-primary-100 font-bold\">Welcome</span>\n            <div className=\"text-primary-100 flex-wrap\">\n              By logging in you accept our&nbsp;\n              <a\n                href=\"/privacy-policy.html\"\n                className=\"text-accent hover:underline\"\n              >\n                Privacy Policy\n              </a>\n              &nbsp;and&nbsp;\n              <a href=\"/terms.html\" className=\"text-accent hover:underline\">\n                Terms of Service\n              </a>\n              .\n            </div>\n          </div>\n          <div className=\"flex flex-col gap-4\">\n            <LoginButton\n              oauthUrl={`${apiBaseUrl}/auth/github/web${queryParams}`}\n            >\n              <SvgSolidGitHub width={20} height={20} />\n              Log in with GitHub\n            </LoginButton>\n            <LoginButton\n              oauthUrl={`${apiBaseUrl}/auth/twitter/web${queryParams}`}\n            >\n              <SvgSolidTwitter width={20} height={20} />\n              Log in with Twitter\n            </LoginButton>\n            {!isElectron() ? (\n              <LoginButton\n                oauthUrl={`${apiBaseUrl}/auth/discord/web${queryParams}`}\n              >\n                <SvgSolidDiscord width={20} height={20} />\n                Log in with Discord\n              </LoginButton>\n            ) : null}\n            {!__prod__ ? (\n              <LoginButton\n                dev\n                onClick={async () => {\n                  // eslint-disable-next-line no-alert\n                  const name = window.prompt(\"username\");\n                  if (!name) {\n                    return;\n                  }\n                  const r = await fetch(\n                    `${apiBaseUrl}/dev/test-info?username=` + name\n                  );\n                  const d = await r.json();\n                  useTokenStore.getState().setTokens({\n                    accessToken: d.accessToken,\n                    refreshToken: d.refreshToken,\n                  });\n                  push(\"/dash\");\n                }}\n                data-testid=\"create-test-user\"\n              >\n                <SvgSolidBug width={20} height={20} />\n                Create a test user\n              </LoginButton>\n            ) : null}\n          </div>\n          {/* <div className=\"flex flex-col gap-3 items-center\">\n          <span className=\"text-primary-100\">Download the app</span>\n          <span className=\"text-primary-300\">unavailable lol</span>\n        </div> */}\n        </div>\n        <div className=\"flex flex-row absolute bottom-0 w-full justify-between px-5 py-5 mt-auto items-center sm:px-7\">\n          <div className=\"hidden sm:flex\">\n            <LgLogo />\n          </div>\n          <div className=\"flex flex-row gap-6 text-primary-300\">\n            <a href=\"/privacy-policy.html\" className=\"hover:text-primary-200\">\n              Privacy policy\n            </a>\n            <a\n              href=\"https://github.com/benawad/dogehouse/issues\"\n              className=\"ml-2 hover:text-primary-200\"\n            >\n              Report a bug\n            </a>\n            <div className=\"flex flex-row gap-6 sm:gap-4\">\n              <a\n                href=\"https://github.com/benawad/dogehouse\"\n                target=\"_blank\"\n                rel=\"noreferrer\"\n              >\n                <SvgSolidGitHub\n                  width={20}\n                  height={20}\n                  className=\"ml-2 cursor-pointer hover:text-primary-200\"\n                />\n              </a>\n              <a\n                href=\"https://discord.gg/wCbKBZF9cV\"\n                target=\"_blank\"\n                rel=\"noreferrer\"\n              >\n                <SvgSolidDiscord\n                  width={20}\n                  height={20}\n                  className=\"ml-2 hover:text-primary-200\"\n                />\n              </a>\n            </div>\n          </div>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/language/LanguagePage.tsx",
    "content": "import React from \"react\";\nimport router from \"next/router\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { LanguageSelector } from \"../../ui/LanguageSelector\";\nimport { PageHeader } from \"../../ui/mobile/MobileHeader\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\n\ninterface LanguagePageProps {}\n\n// This is temp until we have the settings page up and running\nexport const LanguagePage: PageComponent<LanguagePageProps> = () => {\n  const screenType = useScreenType();\n  if (screenType !== \"fullscreen\") router.push(\"/dash\");\n\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title=\"Language\" />\n      <DefaultDesktopLayout\n        mobileHeader={\n          <PageHeader title=\"Language\" onBackClick={() => router.back()} />\n        }\n      >\n        <div className=\"h-full w-full\">\n          <LanguageSelector mobile />\n        </div>\n      </DefaultDesktopLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nLanguagePage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/layouts/DefaultDesktopLayout.tsx",
    "content": "import React from \"react\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { FollowingOnlineController } from \"../dashboard/FollowingOnlineController\";\nimport { ProfileBlockController } from \"../dashboard/ProfileBlockController\";\nimport { MainLayout } from \"./MainLayout\";\nimport { FloatingRoomInfo } from \"./FloatingRoomInfo\";\nimport { TabletSidebar } from \"./TabletSidebar\";\n\ninterface DefaultDesktopLayoutProps {\n  mobileHeader?: React.ReactNode\n}\n\nexport const DefaultDesktopLayout: React.FC<DefaultDesktopLayoutProps> = ({\n  children,\n  mobileHeader = undefined,\n}) => {\n  return (\n    <WaitForWsAndAuth>\n      <MainLayout\n        floatingRoomInfo={<FloatingRoomInfo />}\n        tabletSidebar={<TabletSidebar />}\n        leftPanel={<FollowingOnlineController />}\n        rightPanel={<ProfileBlockController />}\n        mobileHeader={mobileHeader}\n      >\n        {children}\n      </MainLayout>\n    </WaitForWsAndAuth>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/layouts/ElectronHeader.tsx",
    "content": "import isElectron from \"is-electron\";\nimport React, { useState } from \"react\";\nimport {\n  WinMaximizeIcon,\n  WinCloseIcon,\n  WinMinimizeIcon,\n  MacMaximizeIcon,\n  MacCloseIcon,\n  MacMinimizeIcon,\n} from \"../../icons\";\nimport { useHostStore } from \"../../global-stores/useHostStore\";\nimport { MacButton } from \"../../ui/MacButton\";\nimport { WinButton } from \"../../ui/WinButton\";\n\nlet ipcRenderer: any = undefined;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\n\nfunction WinHeader() {\n  return (\n    <div className=\"electron-header z-10\">\n      <div className=\"header-image-cont\">\n        <img\n          className=\"header-image\"\n          src=\"https://github.com/benawad/dogehouse/raw/staging/kibbeh/public/img/doge.png\"\n          width=\"18px\"\n          height=\"18px\"\n          style={{\n            minWidth: \"18px\",\n            minHeight: \"18px\",\n          }}\n        />\n      </div>\n      <div className=\"header-title\">DogeHouse</div>\n      <div className=\"w-full header-drag-region\"></div>\n      <div className=\"header-icons flex flex-row-reverse w-auto\">\n        <WinButton\n          icon={<WinCloseIcon width={10} height={10} />}\n          className=\"hover:bg-accent-hover\"\n          onClick={() => {\n            if (isElectron()) ipcRenderer.send(\"@app/quit\");\n          }}\n        />\n        <WinButton\n          icon={<WinMaximizeIcon width={10} height={10} />}\n          onClick={() => {\n            if (isElectron()) ipcRenderer.send(\"@app/maximize\");\n          }}\n        />\n        <WinButton\n          icon={<WinMinimizeIcon width={10} height={10} />}\n          onClick={() => {\n            if (isElectron()) ipcRenderer.send(\"@app/minimize\");\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n\nfunction MacHeader() {\n  const [hovering, setHovering] = useState(false);\n  return (\n    <div className=\"electron-header z-50\">\n      <div\n        className=\"header-icons-mac flex flex-row space-x-2 content-center ml-5\"\n        onMouseEnter={() => {\n          setHovering(true);\n        }}\n        onMouseLeave={() => {\n          setHovering(false);\n        }}\n      >\n        <MacButton\n          icon={hovering ? <MacCloseIcon /> : null}\n          color=\"red\"\n          onClick={() => {\n            if (isElectron()) ipcRenderer.send(\"@app/quit\");\n          }}\n        />\n        <MacButton\n          icon={hovering ? <MacMinimizeIcon /> : null}\n          color=\"yellow\"\n          onClick={() => {\n            if (isElectron()) ipcRenderer.send(\"@app/minimize\");\n          }}\n        />\n        <MacButton\n          icon={hovering ? <MacMaximizeIcon /> : null}\n          color=\"green\"\n          onClick={() => {\n            if (isElectron()) ipcRenderer.send(\"@app/maximize\");\n          }}\n        />\n      </div>\n      <div className=\"w-full header-drag-region\"></div>\n    </div>\n  );\n}\n\nexport function ElectronHeader() {\n  if (!isElectron() || useHostStore.getState().isLinux) {\n    return null;\n  }\n\n  return useHostStore.getState().isMac ? <MacHeader /> : <WinHeader />;\n}\n"
  },
  {
    "path": "kibbeh/src/modules/layouts/FloatingRoomInfo.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React, { useRef } from \"react\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { SolidDeafened, SolidDeafenedOff, SolidMicrophone } from \"../../icons\";\nimport SvgSolidMicrophoneOff from \"../../icons/SolidMicrophoneOff\";\nimport { useCurrentRoomFromCache } from \"../../shared-hooks/useCurrentRoomFromCache\";\nimport { useCurrentRoomInfo } from \"../../shared-hooks/useCurrentRoomInfo\";\nimport { useSetDeaf } from \"../../shared-hooks/useSetDeaf\";\nimport { useSetMute } from \"../../shared-hooks/useSetMute\";\nimport { BoxedIcon } from \"../../ui/BoxedIcon\";\nimport { MultipleUsers } from \"../../ui/UserAvatar\";\nimport { a, useSpring, config } from \"react-spring\";\nimport { useDrag } from \"react-use-gesture\";\nimport { useBoundingClientRect } from \"../../shared-hooks/useBoundingClientRect\";\nimport { useLeaveRoom } from \"../../shared-hooks/useLeaveRoom\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useMediaQuery } from \"react-responsive\";\n\nexport const FloatingRoomInfo: React.FC = () => {\n  const data = useCurrentRoomFromCache();\n  const { canSpeak } = useCurrentRoomInfo();\n  const { muted } = useMuteStore();\n  const setMute = useSetMute();\n  const { deafened } = useDeafStore();\n  const setDeaf = useSetDeaf();\n  const router = useRouter();\n  const { leaveRoom } = useLeaveRoom();\n  const is1Cols = useMediaQuery({ minWidth: 800 });\n\n  const [{ y }, api] = useSpring(() => ({ y: 0 }));\n  const floatingRef = useRef(null);\n  const bbox = useBoundingClientRect(floatingRef);\n\n  const close = () => {\n    api({\n      y: bbox ? bbox.height : 60,\n      immediate: false,\n      config: { ...config.default },\n      onRest: () => leaveRoom(),\n    });\n  };\n\n  const bind = useDrag(\n    ({ last, down, movement: [, my] }) => {\n      api.start({\n        y: down ? my : 0,\n        config: { mass: 1, tension: 500, friction: 50 },\n      });\n\n      if (last && bbox) {\n        if (y.get() > bbox.height / 3) {\n          close();\n        }\n      }\n    },\n    {\n      axis: \"y\",\n      bounds: {\n        top: 0,\n        left: 0,\n        right: 0,\n      },\n      useTouch: true,\n    }\n  );\n\n  if (!data || \"error\" in data) {\n    return null;\n  }\n\n  const { room } = data;\n\n  const avatars =\n    \"peoplePreviewList\" in room\n      ? room.peoplePreviewList\n          .map((x) => x.avatarUrl!)\n          .slice(0, 2)\n          .filter((x) => x !== null)\n      : [];\n\n  const bgStyles = {\n    opacity: y.to([0, bbox ? bbox.height : 60], [1, 0], \"clamp\"),\n  };\n\n  return (\n    <a.div\n      data-testid=\"floating-room-container\"\n      className=\"flex fixed left-0 bg-primary-900 items-center w-full border-t border-primary-700 px-3 justify-between animate-breathe-slow\"\n      style={{\n        bottom: is1Cols ? 0 : 60,\n        zIndex: 9,\n        y,\n        ...bgStyles,\n      }}\n      {...bind()}\n      ref={floatingRef}\n    >\n      <div className=\"flex overflow-hidden\">\n        <div className=\"mr-2\">\n          <MultipleUsers srcArray={avatars} />\n        </div>\n        <button\n          onClick={() => {\n            router.push(`/room/${room.id}`);\n          }}\n          style={{ minWidth: 100 }}\n          className=\"truncate text-primary-100 text-left font-bold mr-3\"\n        >\n          {room.name}\n        </button>\n      </div>\n\n      <div className=\"flex py-2 overflow-hidden\">\n        {canSpeak ? (\n          <BoxedIcon\n            data-testid=\"mute\"\n            hover\n            onClick={() => {\n              setMute(!muted);\n            }}\n            className={`w-7 mr-2 ${\n              !muted && !deafened ? \"bg-accent text-button\" : \"\"\n            }`}\n          >\n            {muted || deafened ? (\n              <SvgSolidMicrophoneOff />\n            ) : (\n              <SolidMicrophone />\n            )}\n          </BoxedIcon>\n        ) : null}\n        <BoxedIcon\n          data-testid=\"deafen\"\n          hover\n          onClick={() => {\n            setDeaf(!deafened);\n          }}\n          className={`w-7 ${deafened ? \"bg-accent\" : \"\"}`}\n        >\n          {deafened ? <SolidDeafenedOff /> : <SolidDeafened />}\n        </BoxedIcon>\n      </div>\n    </a.div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/layouts/GridPanels.tsx",
    "content": "import isElectron from \"is-electron\";\nimport React, { FC, useContext } from \"react\";\nimport { useIsElectronMobile } from \"../../global-stores/useElectronMobileStore\";\nimport { useHostStore } from \"../../global-stores/useHostStore\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { FixedGridPanel, GridPanel } from \"../../ui/GridPanel\";\nimport LeftHeader from \"../../ui/header/LeftHeader\";\nimport { MiddleHeader } from \"../../ui/header/MiddleHeader\";\nimport RightHeader from \"../../ui/header/RightHeader\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\n\ninterface LeftPanelProps {}\n\nconst HeaderWrapper: FC = ({ children }) => (\n  <div className={`flex mb-7 h-6 items-center`}>{children}</div>\n);\n\nexport const LeftPanel: React.FC<LeftPanelProps> = ({ children }) => {\n  return (\n    <FixedGridPanel>\n      <HeaderWrapper>\n        <LeftHeader />\n      </HeaderWrapper>\n      {children}\n    </FixedGridPanel>\n  );\n};\n\nexport const MiddlePanel: React.FC<\n  LeftPanelProps & { stickyChildren?: React.ReactNode }\n> = ({ stickyChildren, children }) => {\n  const screenType = useScreenType();\n  return (\n    <GridPanel>\n      <div\n        className={\n          !(screenType === \"fullscreen\" && !stickyChildren)\n            ? `flex sticky w-full flex-col z-10 bg-primary-900 pt-5`\n            : \"\"\n        }\n        style={useIsElectronMobile() ? { marginTop: \"45px\" } : { top: \"0px\" }}\n      >\n        {screenType !== \"fullscreen\" ? (\n          <HeaderWrapper>\n            <MiddleHeader />\n          </HeaderWrapper>\n        ) : (\n          \"\"\n        )}\n        {stickyChildren}\n      </div>\n      {children}\n    </GridPanel>\n  );\n};\n\nexport const RightPanel: React.FC<LeftPanelProps> = ({ children }) => {\n  const { conn } = useContext(WebSocketContext);\n  return (\n    <FixedGridPanel>\n      <HeaderWrapper>{conn ? <RightHeader /> : null}</HeaderWrapper>\n      {children}\n    </FixedGridPanel>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/layouts/MainLayout.tsx",
    "content": "import isElectron from \"is-electron\";\nimport router from \"next/router\";\nimport React from \"react\";\nimport { useIsElectronMobile } from \"../../global-stores/useElectronMobileStore\";\nimport { useHostStore } from \"../../global-stores/useHostStore\";\nimport {\n  SolidCalendar,\n  SolidHome,\n  SolidPersonAdd,\n  SolidPlus,\n  SolidUser,\n} from \"../../icons\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { MainInnerGrid } from \"../../ui/MainGrid\";\nimport { AccountOverlay } from \"../../ui/mobile/AccountOverlay\";\nimport { ProfileHeader } from \"../../ui/mobile/MobileHeader\";\nimport { MobileNav } from \"../../ui/mobile/MobileNav\";\nimport { ElectronHeader } from \"./ElectronHeader\";\nimport { FloatingRoomInfo } from \"./FloatingRoomInfo\";\nimport { LeftPanel, RightPanel } from \"./GridPanels\";\nimport { TabletSidebar } from \"./TabletSidebar\";\n\ninterface MainLayoutProps {\n  floatingRoomInfo?: React.ReactNode;\n  tabletSidebar?: React.ReactNode;\n  leftPanel?: React.ReactNode;\n  rightPanel?: React.ReactNode;\n  mobileHeader?: React.ReactNode /** This is an optional parameter in-case you want a custom mobile header (e.g a search header) */;\n  plusButtonURL?: string /** This adds a plus button in the bottom mobile nav */;\n}\n\nexport const MainLayout: React.FC<MainLayoutProps> = ({\n  children,\n  leftPanel = <div />,\n  rightPanel = <div />,\n  tabletSidebar = <TabletSidebar />,\n  floatingRoomInfo = <FloatingRoomInfo />,\n  mobileHeader,\n  plusButtonURL,\n}) => {\n  const screenType = useScreenType();\n  const conn = useConn()!;\n  const me = conn ? conn.user : undefined;\n  const mHeader = mobileHeader || (\n    <ProfileHeader\n      avatar={me ? me.avatarUrl : \"https://dogehouse.tv/favicon.ico\"}\n      onSearchClick={() => router.push(\"/search\")}\n    />\n  );\n\n  const items = [\n    { icon: <SolidHome />, targetPath: \"/dash\" },\n    { icon: <SolidCalendar />, targetPath: \"/scheduled-rooms\" },\n  ];\n\n  if (plusButtonURL) {\n    items.push({ icon: <SolidPlus />, targetPath: plusButtonURL });\n  }\n\n  if (me) {\n    items.push({\n      icon: <SolidUser />,\n      targetPath: `/u/${me.username}/following-online`,\n    });\n  }\n\n  let middle = null;\n  let prepend = null;\n\n  switch (screenType) {\n    case \"3-cols\":\n      middle = (\n        <>\n          <LeftPanel>{leftPanel}</LeftPanel>\n          {children}\n          <RightPanel>{rightPanel}</RightPanel>\n        </>\n      );\n      break;\n    case \"2-cols\":\n      middle = (\n        <>\n          <LeftPanel>{tabletSidebar}</LeftPanel>\n          {children}\n          <RightPanel>{rightPanel}</RightPanel>\n        </>\n      );\n      break;\n    case \"1-cols\":\n      middle = (\n        <>\n          <LeftPanel>{tabletSidebar}</LeftPanel>\n          {children}\n          {floatingRoomInfo}\n        </>\n      );\n      break;\n    case \"fullscreen\":\n      prepend = (\n        <>\n          {mHeader}\n          <MobileNav items={items}></MobileNav>\n        </>\n      );\n      middle = (\n        <>\n          {children}\n          {floatingRoomInfo}\n          <AccountOverlay />\n        </>\n      );\n  }\n\n  return (\n    <>\n      <ElectronHeader />\n      <div\n        className={`fixed left-0 w-full z-10`}\n        style={\n          isElectron() && !useHostStore.getState().isLinux\n            ? { top: 30 }\n            : { top: 0 }\n        }\n      >\n        {prepend}\n      </div>\n      <div\n        className={\n          isElectron() && !useHostStore.getState().isLinux\n            ? `default-desktop-layout flex flex-col items-center w-full scrollbar-thin scrollbar-thumb-primary-700 ${\n                prepend ? \"mb-7\" : \"\"\n              }`\n            : `flex flex-col items-center w-full scrollbar-thin scrollbar-thumb-primary-700 ${\n                prepend ? \"mt-8 mb-7\" : \"\"\n              }`\n        }\n        style={useIsElectronMobile() ? { marginTop: \"38px\" } : {}}\n      >\n        <MainInnerGrid>{middle}</MainInnerGrid>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/layouts/TabletSidebar.tsx",
    "content": "import React, { useState } from \"react\";\nimport { SolidPlus } from \"../../icons\";\nimport { ApiPreloadLink } from \"../../shared-components/ApiPreloadLink\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { BoxedIcon } from \"../../ui/BoxedIcon\";\nimport { SingleUser } from \"../../ui/UserAvatar\";\n\ninterface FriendsOnlineControllerProps {}\n\nconst Page: React.FC<{\n  cursor: number;\n  onLoadMore: (cursor: number) => void;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n}> = ({ cursor, isLastPage, isOnlyPage, onLoadMore }) => {\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"getMyFollowing\", cursor],\n    {\n      refetchOnMount: \"always\",\n    },\n    [cursor]\n  );\n\n  if (isOnlyPage && !isLoading && !data?.users.length) {\n    return null;\n  }\n\n  return (\n    <>\n      {data?.users.map((u) => (\n        <div key={u.id} className=\"flex pb-3 w-full justify-center\">\n          <ApiPreloadLink route=\"profile\" data={{ username: u.username }}>\n            <SingleUser\n              size=\"sm\"\n              isOnline={u.online}\n              src={u.avatarUrl}\n              username={u.username}\n            />\n          </ApiPreloadLink>\n        </div>\n      ))}\n      {isLastPage && data?.nextCursor ? (\n        <div className=\"flex justify-center\">\n          <BoxedIcon circle onClick={() => onLoadMore(data!.nextCursor!)}>\n            <SolidPlus />\n          </BoxedIcon>\n        </div>\n      ) : null}\n    </>\n  );\n};\n\nexport const TabletSidebar: React.FC<FriendsOnlineControllerProps> = ({}) => {\n  const [cursors, setCursors] = useState<number[]>([0]);\n  const conn = useConn();\n\n  if (!conn) {\n    return null;\n  }\n\n  return (\n    <div\n      data-testid=\"tablet-sidebar-container\"\n      className=\"pb-5 w-full flex flex-col flex-1 overflow-y-auto text-primary-100\"\n    >\n      {cursors.map((c, i) => (\n        <Page\n          key={c}\n          cursor={c}\n          onLoadMore={(nc) => setCursors([...cursors, nc])}\n          isLastPage={i === cursors.length - 1}\n          isOnlyPage={cursors.length === 1}\n        />\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/AudioDebugConsumerSection.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { Button } from \"../../ui/Button\";\nimport { useConsumerStore } from \"../webrtc/stores/useConsumerStore\";\n\ninterface AudioDebugConsumerSectionProps {\n  userId: string;\n}\n\nexport const AudioDebugConsumerSection: React.FC<AudioDebugConsumerSectionProps> = ({\n  userId,\n}) => {\n  const [, rerender] = useState(0);\n  const { consumerMap } = useConsumerStore();\n  const data = consumerMap[userId];\n\n  useEffect(() => {\n    const id = setInterval(() => {\n      rerender((c) => c + 1);\n    }, 1000);\n\n    return () => {\n      clearInterval(id);\n    };\n  }, []);\n\n  if (!data) {\n    return null;\n  }\n\n  const { audioRef: r } = data;\n\n  return (\n    <div className=\"w-full\">\n      <pre className=\"text-primary-100 \">\n        {r\n          ? JSON.stringify(\n              {\n                currentTime: r.currentTime,\n                paused: r.paused,\n                ended: r.ended,\n                readyState: r.readyState,\n                duration: r.duration,\n                volume: r.volume,\n              },\n              null,\n              2\n            )\n          : \"no audio ref\"}\n      </pre>\n      <Button\n        onClick={() => {\n          r?.play().catch((err) =>\n            console.log(\"force play audio error: \", err)\n          );\n        }}\n        className={`w-full my-1 text-base`}\n        color=\"secondary\"\n      >\n        Force play audio\n      </Button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/BlockedFromRoomUsers.tsx",
    "content": "import React from \"react\";\nimport { useMutation, useQuery, useQueryClient } from \"react-query\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"../../ui/Button\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { SingleUser } from \"../../ui/UserAvatar\";\n\ninterface BlockedFromRoomUsersProps {}\n\nexport const GET_BLOCKED_FROM_ROOM_USERS = \"get_blocked_from_room_users\";\n\nconst UnbanButton = ({\n  userId,\n  offset,\n}: {\n  userId: string;\n  offset: number;\n}) => {\n  const updater = useTypeSafeUpdateQuery();\n  const { mutateAsync, isLoading } = useTypeSafeMutation(\"unbanFromRoom\", {\n    onSuccess: () => {\n      updater([\"getBlockedFromRoomUsers\", offset], (d) => {\n        if (!d) {\n          return d;\n        }\n\n        return {\n          ...d,\n          users: d.users.filter((x) => x.id !== userId),\n        };\n      });\n    },\n  });\n  const { t } = useTypeSafeTranslation();\n\n  return (\n    <Button\n      loading={isLoading}\n      onClick={() => {\n        mutateAsync([userId]);\n      }}\n      size={`small`}\n    >\n      {t(\"components.blockedFromRoomUsers.unban\")}\n    </Button>\n  );\n};\nexport const BlockedFromRoomUsersPage: React.FC<{\n  offset: number;\n  onLoadMore: (newOffset: number) => void;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n}> = ({ offset, onLoadMore, isOnlyPage, isLastPage }) => {\n  const { isLoading, data } = useTypeSafeQuery(\n    [\"getBlockedFromRoomUsers\", offset],\n    { enabled: false },\n    [offset]\n  );\n  const { t } = useTypeSafeTranslation();\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (isOnlyPage && data?.users.length === 0) {\n    return (\n      <InfoText className={`mt-2`}>\n        {t(\"components.blockedFromRoomUsers.noBans\")}\n      </InfoText>\n    );\n  }\n\n  if (!data) {\n    return null;\n  }\n\n  return (\n    <>\n      {data.users.map((profile) => (\n        <div\n          className={`flex border-b border-solid w-full py-4 px-2 items-center`}\n          key={profile.id}\n        >\n          <div className=\"flex\">\n            <SingleUser size=\"md\" src={profile.avatarUrl} />\n          </div>\n          <div className={`flex ml-4 flex-1 mr-4`}>\n            <div className={`flex text-lg font-bold`}>\n              {profile.displayName}\n            </div>\n            <div style={{ color: \"\" }} className={`flex font-mono font-light`}>\n              &nbsp;(@{profile.username})\n            </div>\n          </div>\n          <UnbanButton offset={offset} userId={profile.id} />\n        </div>\n      ))}\n      {isLastPage && data.nextCursor ? (\n        <div className={`flex items-center justify-center mt-4`}>\n          <Button\n            size=\"small\"\n            onClick={() => {\n              onLoadMore(data.nextCursor!);\n            }}\n          >\n            {t(\"common.loadMore\")}\n          </Button>\n        </div>\n      ) : null}\n    </>\n  );\n};\n\nexport const BlockedFromRoomUsers: React.FC<BlockedFromRoomUsersProps> = ({}) => {\n  const [offsets, setOffsets] = React.useState([0]);\n  const { t } = useTypeSafeTranslation();\n\n  return (\n    <>\n      <div className={`flex mt-4 flex-col text-primary-100 pt-3`}>\n        <h1 className={`text-xl`}>\n          {t(\"components.blockedFromRoomUsers.header\")}\n        </h1>\n        <div className=\"flex flex-col\">\n          {offsets.map((offset, i) => (\n            <BlockedFromRoomUsersPage\n              key={offset}\n              offset={offset}\n              isLastPage={i === offsets.length - 1}\n              isOnlyPage={offsets.length === 1}\n              onLoadMore={(o) => setOffsets([...offsets, o])}\n            />\n          ))}\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/InviteRoomPage.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React, { useRef, useState } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { SolidFriends } from \"../../icons\";\nimport { isServer } from \"../../lib/isServer\";\nimport { ApiPreloadLink } from \"../../shared-components/ApiPreloadLink\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafePrefetch } from \"../../shared-hooks/useTypeSafePrefetch\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { Button } from \"../../ui/Button\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { Input } from \"../../ui/Input\";\nimport { RoomCard } from \"../../ui/RoomCard\";\nimport { SingleUser } from \"../../ui/UserAvatar\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { useGetRoomByQueryParam } from \"./useGetRoomByQueryParam\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { FeedHeader } from \"../../ui/FeedHeader\";\n\ninterface InviteRoomPageProps {}\n\nconst InviteButton: React.FC<{ onClick: () => void }> = ({ onClick }) => {\n  const [invited, setInvited] = useState(false);\n  const { t } = useTypeSafeTranslation();\n  return (\n    <Button\n      size=\"small\"\n      disabled={invited}\n      onClick={() => {\n        onClick();\n        setInvited(true);\n      }}\n    >\n      {invited\n        ? t(\"components.inviteButton.invited\")\n        : t(\"components.inviteButton.inviteToRoom\")}\n    </Button>\n  );\n};\n\nconst Page = ({\n  cursor,\n  isLastPage,\n  onLoadMore,\n}: {\n  cursor: number;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n  onLoadMore: (o: number) => void;\n}) => {\n  const conn = useWrappedConn();\n  const { t } = useTypeSafeTranslation();\n  const { isLoading, data } = useTypeSafeQuery(\n    [\"getInviteList\", cursor],\n    {\n      staleTime: Infinity,\n      enabled: !isServer,\n      refetchOnMount: \"always\",\n    },\n    [cursor]\n  );\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (!data) {\n    return null;\n  }\n\n  return (\n    <>\n      <HeaderController embed={{}} title=\"Invite\" />\n      {data.users.map((user) => (\n        <div key={user.id} className=\"flex items-center mb-6\">\n          <div className=\"flex\">\n            <SingleUser size=\"md\" src={user.avatarUrl} />\n          </div>\n          <div className=\"flex px-4 flex-1\">\n            <ApiPreloadLink route=\"profile\" data={{ username: user.username }}>\n              <div className=\"flex flex-col\">\n                <div className=\"flex text-primary-100\">{user.displayName}</div>\n                <div className=\"flex text-primary-200\">@{user.username}</div>\n              </div>\n            </ApiPreloadLink>\n          </div>\n          <div className=\"block\">\n            <InviteButton\n              onClick={() => {\n                conn.mutation.inviteToRoom(user.id);\n              }}\n            />\n          </div>\n        </div>\n      ))}\n      {isLastPage && data.nextCursor ? (\n        <div className={`flex justify-center py-5`}>\n          <Button\n            size=\"small\"\n            onClick={() => {\n              onLoadMore(data.nextCursor!);\n            }}\n          >\n            {t(\"common.loadMore\")}\n          </Button>\n        </div>\n      ) : null}\n    </>\n  );\n};\n\nexport const InviteRoomPage: PageComponent<InviteRoomPageProps> = ({}) => {\n  const { data, isLoading } = useGetRoomByQueryParam();\n  const { t } = useTypeSafeTranslation();\n  const inputRef = useRef<HTMLInputElement>(null);\n  const [copied, setCopied] = useState(false);\n  const [cursors, setCursors] = useState([0]);\n\n  if (isLoading || !data || \"error\" in data) {\n    return (\n      <DefaultDesktopLayout>\n        <MiddlePanel>\n          <CenterLoader />\n        </MiddlePanel>\n      </DefaultDesktopLayout>\n    );\n  }\n\n  const { room } = data;\n  const url = window.location.origin + `/room/${room.id}`;\n\n  let buttonText = \"copy\";\n\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-ignore\n  if (navigator.share) {\n    buttonText = \"share link to room\";\n  } else if (copied) {\n    buttonText = \"copied\";\n  }\n\n  return (\n    <DefaultDesktopLayout>\n      <MiddlePanel>\n        <>\n          {!navigator.share ? (\n            <div className={`flex text-primary-100 font-bold text-2xl mb-2`}>\n              {t(\"pages.inviteList.shareRoomLink\")}\n            </div>\n          ) : null}\n          <div data-testid=\"container\" className={`mb-8 flex`}>\n            <Input readOnly ref={inputRef} value={url} className=\"mr-2\" />\n            <Button\n              size=\"small\"\n              onClick={() => {\n                if (navigator.share) {\n                  navigator.share({ url });\n                } else {\n                  inputRef.current?.select();\n                  document.execCommand(\"copy\");\n                  setCopied(true);\n                }\n              }}\n            >\n              {buttonText}\n            </Button>\n          </div>\n        </>\n        {cursors.map((cursor, i) => (\n          <Page\n            key={cursor}\n            cursor={cursor}\n            isOnlyPage={cursors.length === 1}\n            onLoadMore={(c) => setCursors([...cursors, c])}\n            isLastPage={i === cursors.length - 1}\n          />\n        ))}\n      </MiddlePanel>\n    </DefaultDesktopLayout>\n  );\n};\n\nInviteRoomPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomChatController.tsx",
    "content": "import React from \"react\";\nimport { useCurrentRoomFromCache } from \"../../shared-hooks/useCurrentRoomFromCache\";\nimport { RoomChat } from \"./chat/RoomChat\";\n\ninterface RoomChatControllerProps {}\n\nexport const RoomChatController: React.FC<RoomChatControllerProps> = ({}) => {\n  const data = useCurrentRoomFromCache();\n\n  if (!data || \"error\" in data) {\n    return null;\n  }\n\n  return <RoomChat {...data} />;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomOpenGraphPreview.tsx",
    "content": "import { Room } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { isServer } from \"../../lib/isServer\";\nimport { HeaderController } from \"../display/HeaderController\";\n\ninterface RoomOpenGraphPreviewProps {\n  room: Room | null | undefined;\n}\n\nexport const RoomOpenGraphPreview: React.FC<RoomOpenGraphPreviewProps> = ({\n  room,\n  children,\n}) => {\n  if (isServer && room) {\n    const { name, description } = room;\n    return (\n      <HeaderController title={name} description={description} embed={{}} />\n    );\n  }\n\n  return <>{children}</>;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomPage.tsx",
    "content": "import { JoinRoomAndGetInfoResponse, Room } from \"@dogehouse/kebab\";\nimport router, { useRouter } from \"next/router\";\nimport React, { useState } from \"react\";\nimport { validate } from \"uuid\";\nimport { isServer } from \"../../lib/isServer\";\nimport { defaultQueryFn } from \"../../lib/defaultQueryFn\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { FollowingOnlineController } from \"../dashboard/FollowingOnlineController\";\nimport { MainLayout } from \"../layouts/MainLayout\";\nimport { TabletSidebar } from \"../layouts/TabletSidebar\";\nimport { RoomChatController } from \"./RoomChatController\";\nimport { RoomOpenGraphPreview } from \"./RoomOpenGraphPreview\";\nimport { RoomPanelController } from \"./RoomPanelController\";\nimport { UserPreviewModalProvider } from \"./UserPreviewModalProvider\";\nimport { PageHeader } from \"../../ui/mobile/MobileHeader\";\nimport { useLeaveRoom } from \"../../shared-hooks/useLeaveRoom\";\nimport { useConn } from \"../../shared-hooks/useConn\";\n\ninterface RoomPageProps {\n  room?: Room;\n}\n\nexport const RoomPage: PageComponent<RoomPageProps> = ({ room }) => {\n  const { query, back } = useRouter();\n  const key = typeof query.id === \"string\" ? query.id : \"\";\n  const { leaveRoom } = useLeaveRoom();\n  const conn = useConn();\n  const [roomData, setRoomData] = useState(\n    undefined as JoinRoomAndGetInfoResponse | undefined\n  );\n  const [showMobileEditModal, setShowMobileEditModal] = useState(false);\n\n  return (\n    <RoomOpenGraphPreview room={room}>\n      <WaitForWsAndAuth>\n        <UserPreviewModalProvider>\n          <MainLayout\n            floatingRoomInfo={null}\n            tabletSidebar={<TabletSidebar />}\n            leftPanel={<FollowingOnlineController />}\n            rightPanel={<RoomChatController />}\n            mobileHeader={\n              <PageHeader\n                title={\n                  <>\n                    <div\n                      className=\"text-center absolute flex flex-col left-1/2 top-1/2 transform translate-x-n1/2 translate-y-n1/2 w-3/5\"\n                      onClick={() => roomData?.room.creatorId === conn.user.id ? setShowMobileEditModal(true) : \"\"}\n                    >\n                      <span className=\"line-clamp-1\">{roomData?.room.name}</span>\n                      {roomData && (\n                        <span\n                          className={\"text-sm text-center font-normal truncate\"}\n                        >\n                          with{\" \"}\n                          <span className={\"font-bold truncate\"}>\n                            {\n                              roomData?.users.find(\n                                (x: any) => x.id === roomData?.room.creatorId\n                              )?.username\n                            }\n                          </span>\n                        </span>\n                      )}\n                    </div>\n                    <button\n                      className={\n                        \"absolute right-3 top-1/2 transform translate-y-n1/2 font-bold text-accent\"\n                      }\n                      style={{ fontSize: \"14px\" }}\n                      onClick={() => {\n                        router.push(\"/dash\");\n                        leaveRoom();\n                      }}\n                    >\n                      Leave\n                    </button>\n                  </>\n                }\n                onBackClick={() => back()}\n              />\n            }\n          >\n            <RoomPanelController\n              key={key}\n              setRoomData={setRoomData}\n              showMobileEditModal={showMobileEditModal}\n              setShowMobileEditModal={setShowMobileEditModal}\n            />\n          </MainLayout>\n        </UserPreviewModalProvider>\n      </WaitForWsAndAuth>\n    </RoomOpenGraphPreview>\n  );\n};\n\nRoomPage.ws = true;\n// ssr\nRoomPage.getInitialProps = async ({ query }) => {\n  const key =\n    typeof query.id === \"string\" && validate(query.id) ? query.id : \"\";\n  let room = null;\n\n  if (isServer && key) {\n    try {\n      const resp = await defaultQueryFn({ queryKey: `/room/${key}` });\n      if (\"room\" in resp) {\n        room = resp.room;\n      }\n    } catch {}\n  }\n\n  return { room };\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomPanelController.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { RoomHeader } from \"../../ui/RoomHeader\";\nimport { CreateRoomModal } from \"../dashboard/CreateRoomModal\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { useRoomChatStore } from \"./chat/useRoomChatStore\";\nimport { RoomPanelIconBarController } from \"./RoomPanelIconBarController\";\nimport { RoomUsersPanel } from \"./RoomUsersPanel\";\nimport { useGetRoomByQueryParam } from \"./useGetRoomByQueryParam\";\nimport { UserPreviewModal } from \"./UserPreviewModal\";\n\ninterface RoomPanelControllerProps {\n  setRoomData?: React.Dispatch<\n    React.SetStateAction<JoinRoomAndGetInfoResponse | undefined>\n  >;\n  showMobileEditModal: boolean;\n  setShowMobileEditModal: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const RoomPanelController: React.FC<RoomPanelControllerProps> = ({\n  setRoomData,\n  showMobileEditModal,\n  setShowMobileEditModal,\n}) => {\n  const conn = useConn();\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const [showEditModal, setShowEditModal] = useState(false);\n  const { data, isLoading } = useGetRoomByQueryParam();\n  const open = useRoomChatStore((s) => s.open);\n  const screenType = useScreenType();\n\n  if (isLoading || !currentRoomId) {\n    return (\n      <>\n        <MiddlePanel>\n          <CenterLoader />\n        </MiddlePanel>\n      </>\n    );\n  }\n\n  if (!data || \"error\" in data) {\n    return null;\n  }\n\n  const roomCreator = data.users.find((x: any) => x.id === data.room.creatorId);\n  if (setRoomData) setRoomData(data);\n\n  return (\n    <>\n      {showEditModal || showMobileEditModal ? (\n        <CreateRoomModal\n          onRequestClose={() => {\n            setShowEditModal(false);\n            setShowMobileEditModal(false);\n          }}\n          edit\n          data={{\n            name: data.room.name,\n            description: data.room.description || \"\",\n            privacy: data.room.isPrivate ? \"private\" : \"public\",\n          }}\n        />\n      ) : null}\n      <HeaderController embed={{}} title={data.room.name} />\n      <MiddlePanel\n        stickyChildren={\n          screenType !== \"fullscreen\" ? (\n            <RoomHeader\n              onTitleClick={\n                data.room.creatorId === conn.user.id\n                  ? () => setShowEditModal(true)\n                  : undefined\n              }\n              title={data.room.name}\n              description={data.room.description || \"\"}\n              names={roomCreator ? [roomCreator.username] : []}\n            />\n          ) : (\n            \"\"\n          )\n        }\n      >\n        <UserPreviewModal {...data} />\n        {screenType === \"fullscreen\" && open ? null : (\n          <RoomUsersPanel {...data} />\n        )}\n        <div\n          className={`sticky bottom-0 pb-7 bg-primary-900 ${\n            (screenType === \"fullscreen\" || screenType === \"1-cols\") && open\n              ? \"flex-1\"\n              : \"\"\n          }`}\n        >\n          <RoomPanelIconBarController {...data} />\n        </div>\n      </MiddlePanel>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomPanelIconBarController.tsx",
    "content": "import { JoinRoomAndGetInfoResponse, RoomUser, wrap } from \"@dogehouse/kebab\";\nimport { useRouter } from \"next/router\";\nimport React, { useMemo, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { SolidPlus } from \"../../icons\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useCurrentRoomInfo } from \"../../shared-hooks/useCurrentRoomInfo\";\nimport { useLeaveRoom } from \"../../shared-hooks/useLeaveRoom\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { useSetDeaf } from \"../../shared-hooks/useSetDeaf\";\nimport { useSetMute } from \"../../shared-hooks/useSetMute\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafePrefetch } from \"../../shared-hooks/useTypeSafePrefetch\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { RoomPanelIconBar } from \"../../ui/RoomPanelIconBar\";\nimport { RoomChatInput } from \"./chat/RoomChatInput\";\nimport { RoomChatList } from \"./chat/RoomChatList\";\nimport { RoomChatMentions } from \"./chat/RoomChatMentions\";\nimport { useRoomChatStore } from \"./chat/useRoomChatStore\";\nimport RoomOverlay from \"./mobile/RoomOverlay\";\nimport { RoomSettingsModal } from \"./RoomSettingModal\";\n\nexport const RoomPanelIconBarController: React.FC<JoinRoomAndGetInfoResponse> = ({\n  users,\n  room,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const { muted } = useMuteStore();\n  const setMute = useSetMute();\n  const { deafened } = useDeafStore();\n  const conn = useConn();\n  const setDeaf = useSetDeaf();\n  const { canSpeak, isCreator, canIAskToSpeak } = useCurrentRoomInfo();\n  const { leaveRoom } = useLeaveRoom();\n  const { push } = useRouter();\n  const prefetch = useTypeSafePrefetch();\n  const { mutateAsync: setListener } = useTypeSafeMutation(\"setListener\");\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const [roomId, setRoomId] = useState(\"\");\n  const [open, toggleOpen] = useRoomChatStore((s) => [s.open, s.toggleOpen]);\n  const screenType = useScreenType();\n  const userMap = useMemo(() => {\n    const map: Record<string, RoomUser> = {};\n    users.forEach((u) => {\n      map[u.id] = u;\n    });\n    return map;\n  }, [users]);\n\n  return (\n    <div className=\"flex flex-col w-full\">\n      <RoomSettingsModal open={!!roomId} onRequestClose={() => setRoomId(\"\")} />\n      {screenType === \"fullscreen\" ? (\n        <RoomOverlay\n          mute={\n            canSpeak\n              ? { isMuted: muted, onMute: () => setMute(!muted) }\n              : undefined\n          }\n          canSpeak={canSpeak}\n          deaf={{ isDeaf: deafened, onDeaf: () => setDeaf(!deafened) }}\n          onInvitePeopleToRoom={() => {\n            push(`/room/[id]/invite`, `/room/${currentRoomId}/invite`);\n          }}\n          onRoomSettings={\n            isCreator\n              ? () => {\n                  prefetch([\"getBlockedFromRoomUsers\", 0]);\n                  setRoomId(currentRoomId!);\n                }\n              : undefined\n          }\n          askToSpeak={\n            canIAskToSpeak ? () => wrap(conn).mutation.askToSpeak() : undefined\n          }\n          setListener={() => setListener([conn.user.id])}\n        />\n      ) : (\n        <RoomPanelIconBar\n          onToggleChat={() => toggleOpen()}\n          mute={\n            canSpeak\n              ? { isMuted: muted, onMute: () => setMute(!muted) }\n              : undefined\n          }\n          deaf={{ isDeaf: deafened, onDeaf: () => setDeaf(!deafened) }}\n          onLeaveRoom={() => {\n            push(\"/dash\");\n            leaveRoom();\n          }}\n          onInvitePeopleToRoom={() => {\n            push(`/room/[id]/invite`, `/room/${currentRoomId}/invite`);\n          }}\n          onRoomSettings={\n            isCreator\n              ? () => {\n                  prefetch([\"getBlockedFromRoomUsers\", 0]);\n                  setRoomId(currentRoomId!);\n                }\n              : undefined\n          }\n        />\n      )}\n      {screenType === \"1-cols\" && open\n        ? createPortal(\n            // this is kind of hard to embed in the page\n            // so tmp solution of portaling this and absolute positioning for fullscreen\n            <div\n              className={`flex absolute flex-col w-full z-30 bg-primary-800 h-full rounded-8`}\n            >\n              <button\n                onClick={() => toggleOpen()}\n                className=\"flex justify-between items-center w-full text-primary-100 p-4 text-2xl\"\n              >\n                <span>{t(\"modules.roomChat.title\")}</span>\n                {/* Just a temporary solution to make close chat ux better, until we have the design in figma */}\n                <SolidPlus className={`transform rotate-45`} />\n              </button>\n              <div className=\"flex overflow-y-auto flex-1\">\n                <div className={`flex flex-1 w-full flex-col mt-4`}>\n                  <RoomChatList room={room} userMap={userMap} />\n                  <RoomChatMentions users={users} />\n                  <RoomChatInput users={users} />\n                </div>\n              </div>\n            </div>,\n            document.querySelector(\"#__next\")!\n          )\n        : null}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomSettingModal.tsx",
    "content": "import { ChatMode } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useCurrentRoomFromCache } from \"../../shared-hooks/useCurrentRoomFromCache\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { Input } from \"../../ui/Input\";\nimport { Modal } from \"../../ui/Modal\";\nimport { NativeSelect } from \"../../ui/NativeSelect\";\nimport { BlockedFromRoomUsers } from \"./BlockedFromRoomUsers\";\n\ninterface RoomSettingsModalProps {\n  open: boolean;\n  onRequestClose: () => void;\n}\n\nexport const RoomSettingsModal: React.FC<RoomSettingsModalProps> = ({\n  open,\n  onRequestClose,\n}) => {\n  const conn = useWrappedConn();\n  const data = useCurrentRoomFromCache();\n  const updater = useTypeSafeUpdateQuery();\n  const { t } = useTypeSafeTranslation();\n\n  const options = [\n    {\n      label: t(\"components.modals.roomSettingsModal.chat.enabled\"),\n      value: \"default\",\n    },\n    {\n      label: t(\"components.modals.roomSettingsModal.chat.disabled\"),\n      value: \"disabled\",\n    },\n    {\n      label: t(\"components.modals.roomSettingsModal.chat.followerOnly\"),\n      value: \"follower_only\",\n    },\n  ];\n\n  return (\n    <Modal isOpen={open} onRequestClose={onRequestClose}>\n      {!data || \"error\" in data ? (\n        <InfoText>something went wrong</InfoText>\n      ) : (\n        <div className={`flex flex-col w-full`}>\n          {/* require ask to speak */}\n          <label className={`flex items-center my-1`} htmlFor=\"auto-speaker\">\n            <input\n              checked={!data.room.autoSpeaker}\n              onChange={(e) => {\n                const autoSpeaker = !e.target.checked;\n                updater([\"joinRoomAndGetInfo\", data.room.id], (d) =>\n                  !d || \"error\" in d\n                    ? d\n                    : { ...d, room: { ...d.room, autoSpeaker } }\n                );\n                conn.mutation.roomUpdate({ autoSpeaker });\n              }}\n              id=\"auto-speaker\"\n              type=\"checkbox\"\n            />\n            <span className={`ml-2 text-primary-100`}>\n              {t(\"components.modals.roomSettingsModal.requirePermission\")}\n            </span>\n          </label>\n\n          <label className={`items-center my-1`} htmlFor=\"chat-cooldown\">\n            <div className={`text-primary-100 mb-1`}>\n              {t(\"components.modals.roomSettingsModal.chatCooldown\")}\n            </div>\n            <Input\n              defaultValue={data.room.chatThrottle}\n              className={`rounded-8 bg-primary-700 h-6`}\n              onBlur={(e) => {\n                const chatThrottle = Number(e.target.value);\n                if (chatThrottle >= 0) {\n                  updater([\"joinRoomAndGetInfo\", data.room.id], (d) =>\n                    !d ? d : { ...d, chatThrottle }\n                  );\n                  conn.mutation.roomUpdate({ chatThrottle });\n                }\n              }}\n              onChange={(e) => {\n                const chatThrottle = Number(e.target.value);\n                if (chatThrottle >= 0) {\n                  updater([\"joinRoomAndGetInfo\", data.room.id], (d) =>\n                    !d ? d : { ...d, chatThrottle }\n                  );\n                }\n              }}\n              id=\"chat-cooldown\"\n              type=\"number\"\n            />\n          </label>\n\n          {/* chat disabled */}\n          <label className={`mt-2`} htmlFor=\"chat-mode\">\n            <div className={`text-primary-100 mb-1`}>\n              {t(\"components.modals.roomSettingsModal.chat.label\")}\n            </div>\n            <NativeSelect\n              value={data.room.chatMode}\n              onChange={(e) => {\n                const chatMode = e.target.value as ChatMode;\n                updater([\"joinRoomAndGetInfo\", data.room.id], (d) => {\n                  return !d || \"error\" in d\n                    ? d\n                    : { ...d, room: { ...d.room, chatMode } };\n                });\n                conn.mutation.roomUpdate({ chatMode });\n              }}\n              id=\"chat-mode\"\n            >\n              {options.map((o) => (\n                <option key={o.value} value={o.value}>\n                  {o.label}&nbsp;&nbsp;&nbsp;\n                </option>\n              ))}\n            </NativeSelect>\n          </label>\n          <BlockedFromRoomUsers />\n        </div>\n      )}\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/RoomUsersPanel.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport isElectron from \"is-electron\";\nimport React, { useEffect, useContext } from \"react\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { RoomSectionHeader } from \"../../ui/RoomSectionHeader\";\nimport { useSplitUsersIntoSections } from \"./useSplitUsersIntoSections\";\nimport { WebSocketContext } from \"../../modules/ws/WebSocketProvider\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { useMediaQuery } from \"react-responsive\";\nimport { AudioDebugPanel } from \"../debugging/AudioDebugPanel\";\nimport { useDebugAudioStore } from \"../../global-stores/useDebugAudio\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { isWebRTCEnabled } from \"../../lib/isWebRTCEnabled\";\nimport { useIsElectronMobile } from \"../../global-stores/useElectronMobileStore\";\n\ninterface RoomUsersPanelProps extends JoinRoomAndGetInfoResponse {}\n\nlet ipcRenderer: any = undefined;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\n\nexport const RoomUsersPanel: React.FC<RoomUsersPanelProps> = (props) => {\n  const {\n    askingToSpeak,\n    listeners,\n    speakers,\n    canIAskToSpeak,\n  } = useSplitUsersIntoSections(props);\n  const { t } = useTypeSafeTranslation();\n  const me = useContext(WebSocketContext).conn?.user;\n  const muted = useMuteStore().muted;\n  const deafened = useDeafStore().deafened;\n  let gridTemplateColumns = \"repeat(5, minmax(0, 1fr))\";\n  const screenType = useScreenType();\n  const isBigFullscreen = useMediaQuery({ minWidth: 640 });\n\n  if (isBigFullscreen && screenType === \"fullscreen\") {\n    gridTemplateColumns = \"repeat(4, minmax(0, 1fr))\";\n  } else if (screenType === \"fullscreen\") {\n    gridTemplateColumns = \"repeat(3, minmax(0, 1fr))\";\n  }\n  useEffect(() => {\n    if (isElectron()) {\n      ipcRenderer.send(\"@room/data\", {\n        currentRoom: props,\n        muted,\n        deafened,\n        me: me || {},\n      });\n    }\n  }, [props, muted, deafened, me]);\n\n  const { debugAudio } = useDebugAudioStore();\n\n  return (\n    <div\n      className={`flex pt-4 px-4 flex-1 ${\n        screenType !== \"fullscreen\" ? \"bg-primary-800\" : \"bg-primary-900\"\n      }`}\n      id={props.room.isPrivate ? \"private-room\" : \"public-room\"}\n      style={useIsElectronMobile() ? { marginTop: \"38px\" } : { top: \"0px\" }}\n    >\n      <div className=\"w-full block\">\n        {!isWebRTCEnabled() ? (\n          <div className=\"text-accent bg-primary-600 p-1 mb-2\">\n            Your browser does not support WebRTC or it is disabled.\n          </div>\n        ) : null}\n        {debugAudio ? <AudioDebugPanel /> : null}\n        <div\n          style={{\n            gridTemplateColumns,\n          }}\n          className={`w-full grid gap-5`}\n        >\n          <RoomSectionHeader\n            title={t(\"pages.room.speakers\")}\n            tagText={\n              \"\" + (canIAskToSpeak ? speakers.length - 1 : speakers.length)\n            }\n          />\n          {speakers}\n          {askingToSpeak.length ? (\n            <RoomSectionHeader\n              title={t(\"pages.room.requestingToSpeak\")}\n              tagText={\"\" + askingToSpeak.length}\n            />\n          ) : null}\n          {askingToSpeak}\n          {listeners.length ? (\n            <RoomSectionHeader\n              title={t(\"pages.room.listeners\")}\n              tagText={\"\" + listeners.length}\n            />\n          ) : null}\n          {listeners}\n          <div className={`flex h-3 w-full col-span-full`}></div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/UserPreviewModal.tsx",
    "content": "import { JoinRoomAndGetInfoResponse, RoomUser, UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport React, { useContext } from \"react\";\nimport { useDebugAudioStore } from \"../../global-stores/useDebugAudio\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useCurrentRoomInfo } from \"../../shared-hooks/useCurrentRoomInfo\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { Modal } from \"../../ui/Modal\";\nimport { Spinner } from \"../../ui/Spinner\";\nimport { VerticalUserInfoWithFollowButton } from \"../user/VerticalUserInfoWithFollowButton\";\nimport { AudioDebugConsumerSection } from \"./AudioDebugConsumerSection\";\nimport { RoomChatMessage, useRoomChatStore } from \"./chat/useRoomChatStore\";\nimport { UserPreviewModalContext } from \"./UserPreviewModalProvider\";\nimport { VolumeSliderController } from \"./VolumeSliderController\";\n\nconst UserPreview: React.FC<{\n  message?: RoomChatMessage;\n  id: string;\n  isMe: boolean;\n  iAmCreator: boolean;\n  iAmMod: boolean;\n  isCreator: boolean;\n  roomPermissions?: RoomUser[\"roomPermissions\"];\n  onClose: () => void;\n}> = ({\n  id,\n  isCreator,\n  isMe,\n  iAmCreator,\n  iAmMod,\n  message,\n  roomPermissions,\n  onClose,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const { mutateAsync: setListener } = useTypeSafeMutation(\"setListener\");\n  const { mutateAsync: changeModStatus } = useTypeSafeMutation(\n    \"changeModStatus\"\n  );\n  const { mutateAsync: changeRoomCreator } = useTypeSafeMutation(\n    \"changeRoomCreator\"\n  );\n  const { mutateAsync: addSpeaker } = useTypeSafeMutation(\"addSpeaker\");\n  const { mutateAsync: deleteRoomChatMessage } = useTypeSafeMutation(\n    \"deleteRoomChatMessage\"\n  );\n  const { mutateAsync: roomBan } = useTypeSafeMutation(\"roomBan\");\n  const { mutateAsync: banFromRoomChat } = useTypeSafeMutation(\n    \"banFromRoomChat\"\n  );\n  const { mutateAsync: unbanFromRoomChat } = useTypeSafeMutation(\n    \"unbanFromRoomChat\"\n  );\n  const { data, isLoading } = useTypeSafeQuery([\"getUserProfile\", id], {}, [\n    id,\n  ]);\n  const bannedUserIdMap = useRoomChatStore((s) => s.bannedUserIdMap);\n  const { debugAudio } = useDebugAudioStore();\n\n  if (isLoading) {\n    return (\n      <div\n        style={{ height: \"400px\", maxHeight: \"100%\" }}\n        className={`flex items-center justify-center w-full`}\n      >\n        <Spinner />\n      </div>\n    );\n  }\n\n  if (!data) {\n    return <div className={`flex p-6 text-center items-center justify-center w-full font-bold text-primary-100`}>This\n      user is gone.</div>;\n  }\n\n  if (\"error\" in data) {\n    const error = data.error;\n\n    let errorMessage = t(\"pages.viewUser.errors.default\");\n\n    switch (error) {\n      case \"blocked\":\n        errorMessage = t(\"pages.viewUser.errors.blocked\");\n        break;\n    }\n\n    return <div\n      className={`flex p-6 text-center items-center justify-center w-full font-bold text-primary-100`}>{errorMessage}</div>;\n  }\n\n  const canDoModStuffOnThisUser =\n    !isMe &&\n    (iAmCreator || iAmMod) &&\n    !isCreator &&\n    (!roomPermissions?.isMod || iAmCreator);\n\n  // [shouldShow, key, onClick, text]\n  const buttonData = [\n    [\n      iAmCreator && !isMe && roomPermissions?.isSpeaker,\n      \"changeRoomCreator\",\n      () => {\n        onClose();\n        changeRoomCreator([id]);\n      },\n      t(\"components.modals.profileModal.makeRoomCreator\"),\n    ],\n    [\n      !isMe && iAmCreator,\n      \"makeMod\",\n      () => {\n        onClose();\n        changeModStatus([id, !roomPermissions?.isMod]);\n      },\n      roomPermissions?.isMod\n        ? t(\"components.modals.profileModal.unmod\")\n        : t(\"components.modals.profileModal.makeMod\"),\n    ],\n    [\n      canDoModStuffOnThisUser &&\n        !roomPermissions?.isSpeaker &&\n        roomPermissions?.askedToSpeak,\n      \"addSpeakerButton\",\n      () => {\n        onClose();\n        addSpeaker([id]);\n      },\n      t(\"components.modals.profileModal.addAsSpeaker\"),\n    ],\n    [\n      canDoModStuffOnThisUser && roomPermissions?.isSpeaker,\n      \"moveToListenerButton\",\n      () => {\n        onClose();\n        setListener([id]);\n      },\n      t(\"components.modals.profileModal.moveToListener\"),\n    ],\n    [\n      canDoModStuffOnThisUser &&\n        !(id in bannedUserIdMap) &&\n        (iAmCreator || !roomPermissions?.isMod),\n      \"banFromChat\",\n      () => {\n        onClose();\n        banFromRoomChat([id]);\n      },\n      t(\"components.modals.profileModal.banFromChat\"),\n    ],\n    [\n      canDoModStuffOnThisUser &&\n        id in bannedUserIdMap &&\n        (iAmCreator || !roomPermissions?.isMod),\n      \"unbanFromChat\",\n      () => {\n        onClose();\n        unbanFromRoomChat([id]);\n      },\n      t(\"components.modals.profileModal.unBanFromChat\"),\n    ],\n    [\n      canDoModStuffOnThisUser && (iAmCreator || !roomPermissions?.isMod),\n      \"banFromRoom\",\n      () => {\n        onClose();\n        roomBan([id]);\n      },\n      t(\"components.modals.profileModal.banFromRoom\"),\n    ],\n    [\n      canDoModStuffOnThisUser && (iAmCreator || !roomPermissions?.isMod),\n      \"banIpFromRoom\",\n      () => {\n        onClose();\n        roomBan([id, true]);\n      },\n      t(\"components.modals.profileModal.banIPFromRoom\"),\n    ],\n    [\n      isMe &&\n        !iAmCreator &&\n        (roomPermissions?.askedToSpeak || roomPermissions?.isSpeaker),\n      \"goBackToListener\",\n      () => {\n        onClose();\n        setListener([id]);\n      },\n      t(\"components.modals.profileModal.goBackToListener\"),\n    ],\n    [\n      !!message,\n      \"deleteMessage\",\n      () => {\n        if (message?.id) {\n          deleteRoomChatMessage([message.userId, message.id]);\n\n          onClose();\n        }\n      },\n      t(\"components.modals.profileModal.deleteMessage\"),\n    ],\n  ] as const;\n\n  return (\n    <div className={`flex flex-col w-full`}>\n      <div className={`flex bg-primary-900 flex-col`}>\n        <VerticalUserInfoWithFollowButton\n          idOrUsernameUsedForQuery={id}\n          user={data as UserWithFollowInfo}\n        />\n      </div>\n      {!isMe && (isCreator || roomPermissions?.isSpeaker) ? (\n        <div className={`flex pb-3 bg-primary-800`}>\n          <VolumeSliderController userId={id} />\n        </div>\n      ) : null}\n      <div className=\"flex px-6 flex-col bg-primary-800\">\n        {debugAudio ? <AudioDebugConsumerSection userId={id} /> : null}\n        {buttonData.map(([shouldShow, key, onClick, text]) => {\n          return shouldShow ? (\n            <Button\n              color=\"secondary\"\n              className={`my-1 text-base`}\n              key={key}\n              onClick={onClick}\n            >\n              {text}\n            </Button>\n          ) : null;\n        })}\n      </div>\n    </div>\n  );\n};\n\nexport const UserPreviewModal: React.FC<JoinRoomAndGetInfoResponse> = ({\n  room,\n  users,\n}) => {\n  const { isCreator: iAmCreator, isMod } = useCurrentRoomInfo();\n  const { data, setData } = useContext(UserPreviewModalContext);\n  const conn = useConn();\n  return (\n    <Modal\n      variant=\"userPreview\"\n      onRequestClose={() => setData(null)}\n      isOpen={!!data}\n    >\n      {!data ? null : (\n        <UserPreview\n          id={data.userId}\n          isCreator={room.creatorId === data.userId}\n          roomPermissions={\n            users.find((u) => u.id === data.userId)?.roomPermissions\n          }\n          iAmCreator={iAmCreator}\n          isMe={conn.user.id === data.userId}\n          iAmMod={isMod}\n          message={data.message}\n          onClose={() => setData(null)}\n        />\n      )}\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/UserPreviewModalProvider.tsx",
    "content": "import React, { useMemo, useState } from \"react\";\nimport { RoomChatMessage } from \"./chat/useRoomChatStore\";\n\ninterface UserProfileOverlayProviderProps {}\n\ntype Data = { userId: string; message?: RoomChatMessage };\n\nexport const UserPreviewModalContext = React.createContext<{\n  data?: Data | null;\n  setData: (x: Data | null) => void;\n}>({ setData: () => {} });\n\nexport const UserPreviewModalProvider: React.FC<UserProfileOverlayProviderProps> = ({\n  children,\n}) => {\n  const [data, setData] = useState<Data | null>(null);\n  return (\n    <UserPreviewModalContext.Provider\n      value={useMemo(() => ({ data, setData }), [data, setData])}\n    >\n      {children}\n    </UserPreviewModalContext.Provider>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/ViewScheduledRoomPage.tsx",
    "content": "import { ScheduledRoom } from \"@dogehouse/kebab\";\nimport router, { useRouter } from \"next/router\";\nimport { validate } from \"uuid\";\nimport React, { useState } from \"react\";\nimport { useQuery, useQueryClient } from \"react-query\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { MainLayout } from \"../layouts/MainLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { EditScheduleRoomModalController } from \"../scheduled-rooms/EditScheduleRoomModalController\";\nimport { ScheduledRoomCard } from \"../scheduled-rooms/ScheduledRoomCard\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { PageHeader } from \"../../ui/mobile/MobileHeader\";\n\ninterface ViewScheduledRoomPageProps {}\n\ntype GetScheduledRoomById = { room: ScheduledRoom | null };\n\nexport const ViewScheduledRoomPage: React.FC<ViewScheduledRoomPageProps> = ({}) => {\n  const queryClient = useQueryClient();\n  const [deleted, setDeleted] = useState(false);\n  const { query } = useRouter();\n  const id = typeof query.id === \"string\" ? query.id : \"\";\n  const key = `/scheduled-room/${id}`;\n  const { data, isLoading } = useQuery<\n    GetScheduledRoomById | { error: string }\n  >(key, { enabled: validate(id) });\n\n  if (!data || isLoading) {\n    return null;\n  }\n\n  if (\"error\" in data || !data.room) {\n    return (\n      <MainLayout>\n        <InfoText>could not find room</InfoText>\n      </MainLayout>\n    );\n  }\n\n  return (\n    <MainLayout mobileHeader={<PageHeader title=\"Scheduled Room\" onBackClick={() => router.push('/dash')}/>}>\n      <HeaderController title={data.room.name} embed={{}} />\n      <MiddlePanel>\n        {deleted ? (\n          <InfoText>deleted</InfoText>\n        ) : (\n          <EditScheduleRoomModalController\n            onScheduledRoom={(_editInfo, values, _resp) => {\n              queryClient.setQueryData<GetScheduledRoomById>(key, {\n                room: {\n                  ...data.room!,\n                  name: values.name,\n                  description: values.description,\n                  scheduledFor: values.scheduledFor.toISOString(),\n                },\n              });\n            }}\n          >\n            {({ onEdit }) => (\n              <ScheduledRoomCard\n                info={data.room!}\n                onDeleteComplete={() => setDeleted(true)}\n                noCopyLinkButton\n                onEdit={() =>\n                  onEdit({ scheduleRoomToEdit: data.room!, cursor: \"\" })\n                }\n              />\n            )}\n          </EditScheduleRoomModalController>\n        )}\n      </MiddlePanel>\n    </MainLayout>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/VolumeSliderController.tsx",
    "content": "import React from \"react\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { VolumeSlider } from \"../../ui/VolumeSlider\";\nimport { useConsumerStore } from \"../webrtc/stores/useConsumerStore\";\n\ninterface VolumeSliderControllerProps {\n  userId: string;\n}\n\nexport const VolumeSliderController: React.FC<VolumeSliderControllerProps> = ({\n  userId,\n}) => {\n  const { consumerMap, setVolume } = useConsumerStore();\n  const consumerInfo = consumerMap[userId];\n  const { t } = useTypeSafeTranslation();\n\n  if (!consumerInfo) {\n    return (\n      <div className={`flex text-primary-300 justify-center w-full py-2`}>\n        {t(\"components.userVolumeSlider.noAudioMessage\")}\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex mt-1 w-full px-4\">\n      <VolumeSlider\n        label\n        max={200}\n        volume={consumerInfo.volume}\n        onDoubleClick={() => setVolume(userId, 100)}\n        onVolume={(n) => setVolume(userId, n)}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/Emote.tsx",
    "content": "import React from \"react\";\nimport { EmoteKeys, emoteMap } from \"./EmoteData\";\n\ninterface CustomEmojiProps {\n  emote: EmoteKeys;\n  style?: React.CSSProperties;\n  title?: string;\n  alt?: string;\n  className?: string;\n  size?: \"small\";\n}\n\nexport const Emote: React.FC<CustomEmojiProps> = ({\n  emote,\n  size,\n  style,\n  title = emote,\n  alt = `:${emote}:`,\n  className,\n}) => {\n  const src = emoteMap[emote.toLowerCase()];\n  let cn = \"\";\n  if (size === \"small\") {\n    cn = `w-3 h-3`;\n  }\n  return src ? (\n    <>\n      <img\n        style={style}\n        className={`inline ${cn} ${className}`}\n        alt={alt}\n        title={title}\n        src={src}\n      />{\" \"}\n    </>\n  ) : (\n    <>{\":\" + emote + \":\"}</>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/EmoteData.ts",
    "content": "export const customEmojis = [\n  {\n    name: \"heart\",\n    short_names: [\"heart\", \"<3\"],\n    keywords: [\"heart\", \"<3\"],\n    imageUrl: \"/emotes/heart.png\",\n  },\n  {\n    name: \"brokenheart\",\n    short_names: [\"brokenHeart\"],\n    keywords: [\"broken\", \"heart\", \"broken heart\", \"brokenHeart\"],\n    imageUrl: \"/emotes/brokenHeart.gif\",\n  },\n  {\n    name: \"obama\",\n    short_names: [\"obama\"],\n    keywords: [\"obama\", \"prism\", \"obamium\"],\n    imageUrl: \"/emotes/obamium.png\",\n  },\n  {\n    name: \"intelxamd\",\n    short_names: [\"intelxamd\"],\n    keywords: [\"intel\", \"amd\", \"intelxamd\"],\n    imageUrl: \"/emotes/IntelxAMD.png\",\n  },\n  {\n    name: \"linus\",\n    short_names: [\"linus\"],\n    keywords: [\"linus\", \"tech\", \"linustechtips\"],\n    imageUrl: \"/emotes/linus.png\",\n  },\n  {\n    name: \"reddogehouse\",\n    short_names: [\"redDogeHouse\"],\n    keywords: [\"red\", \"dogehouse\", \"doge\"],\n    imageUrl: \"/emotes/reddogehouse.png\",\n  },\n  {\n    name: \"this\",\n    short_names: [\"this\"],\n    keywords: [\"this\"],\n    imageUrl: \"/emotes/this.png\",\n  },\n  {\n    name: \"browndogehouse\",\n    short_names: [\"brownDogeHouse\"],\n    keywords: [\"brown\", \"dogehouse\", \"doge\"],\n    imageUrl: \"/emotes/browndogehouse.png\",\n  },\n  {\n    name: \"shut\",\n    short_names: [\"shut\"],\n    keywords: [\"shut\"],\n    imageUrl: \"/emotes/shut.png\",\n  },\n  {\n    name: \"blobwtf\",\n    short_names: [\"blobWtf\"],\n    keywords: [\"blob\", \"wtf\"],\n    imageUrl: \"/emotes/blobwtf.png\",\n  },\n  {\n    name: \"whalethonk\",\n    short_names: [\"WhaleThonk\"],\n    keywords: [\"whale\", \"thonk\", \"thinking\", \"what\"],\n    imageUrl: \"/emotes/whalethonk.png\",\n  },\n  {\n    name: \"pogchamp\",\n    short_names: [\"PogChamp\"],\n    keywords: [\"pogchamp\", \"pog\"],\n    imageUrl: \"/emotes/pogchamp.png\",\n  },\n  {\n    name: \"monkas\",\n    short_names: [\"monkaS\"],\n    keywords: [\"monkas\", \"pepe\"],\n    imageUrl: \"/emotes/monkas.png\",\n  },\n  {\n    name: \"hypers\",\n    short_names: [\"HYPERS\"],\n    keywords: [\"hypers\", \"pepe\"],\n    imageUrl: \"/emotes/hypers.png\",\n  },\n  {\n    name: \"peped\",\n    short_names: [\"pepeD\"],\n    keywords: [\"peped\", \"pepe\"],\n    imageUrl: \"/emotes/peped.gif\",\n  },\n  {\n    name: \"pepega\",\n    short_names: [\"Pepega\"],\n    keywords: [\"pepega\", \"pepe\"],\n    imageUrl: \"/emotes/pepega.png\",\n  },\n  {\n    name: \"peepohappy\",\n    short_names: [\"peepoHappy\"],\n    keywords: [\"peepohappy\", \"peepo\", \"pepe\"],\n    imageUrl: \"/emotes/peepohappy.png\",\n  },\n  {\n    name: \"peepohug\",\n    short_names: [\"peepoHug\"],\n    keywords: [\"peepohug\", \"peepo\", \"pepe\"],\n    imageUrl: \"/emotes/peepohug.png\",\n  },\n  {\n    name: \"sadge\",\n    short_names: [\"Sadge\"],\n    keywords: [\"sadge\", \"pepe\"],\n    imageUrl: \"/emotes/sadge.png\",\n  },\n  {\n    name: \"catjam\",\n    short_names: [\"catJAM\"],\n    keywords: [\"catjam\", \"vibe\"],\n    imageUrl: \"/emotes/catjam.gif\",\n  },\n  {\n    name: \"thonk\",\n    short_names: [\"Thonk\"],\n    keywords: [\"thonk\", \"think\"],\n    imageUrl: \"/emotes/thonk.png\",\n  },\n  {\n    name: \"dogehouse\",\n    short_names: [\"DogeHouse\"],\n    keywords: [\"dogehouse\", \"doge\"],\n    imageUrl: \"/emotes/dogehouse.png\",\n  },\n  {\n    name: \"sadhouse\",\n    short_names: [\"SadHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"sadhouse\"],\n    imageUrl: \"/emotes/sadhouse.png\",\n  },\n  {\n    name: \"coolhouse\",\n    short_names: [\"CoolHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"coolhouse\"],\n    imageUrl: \"/emotes/coolhouse.png\",\n  },\n  {\n    name: \"winkhouse\",\n    short_names: [\"WinkHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"winkhouse\"],\n    imageUrl: \"/emotes/winkhouse.png\",\n  },\n  {\n    name: \"suprisehouse\",\n    short_names: [\"SupriseHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"suprisehouse\", \"shock\"],\n    imageUrl: \"/emotes/suprisehouse.png\",\n  },\n  {\n    name: \"neutralhouse\",\n    short_names: [\"NeutralHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"neutralhouse\"],\n    imageUrl: \"/emotes/neutralhouse.png\",\n  },\n  {\n    name: \"waytoodank\",\n    short_names: [\"WAYTOODANK\"],\n    keywords: [\"dank\", \"feelsdankman\", \"waytoodank\"],\n    imageUrl: \"/emotes/waytoodank.gif\",\n  },\n  {\n    name: \"cryptobtc\",\n    short_names: [\"CryptoBTC\"],\n    keywords: [\"crypto\", \"btc\", \"bitcoin\"],\n    imageUrl: \"/emotes/cryptoBTC.png\",\n  },\n  {\n    name: \"cryptoeth\",\n    short_names: [\"CryptoETH\"],\n    keywords: [\"crypto\", \"eth\", \"ethereum\"],\n    imageUrl: \"/emotes/cryptoETH.png\",\n  },\n  {\n    name: \"cryptobnb\",\n    short_names: [\"CryptoBNB\"],\n    keywords: [\"crypto\", \"bnb\", \"binance\"],\n    imageUrl: \"/emotes/cryptoBNB.png\",\n  },\n  {\n    name: \"cryptoltc\",\n    short_names: [\"CryptoLTC\"],\n    keywords: [\"crypto\", \"ltc\", \"litecoin\"],\n    imageUrl: \"/emotes/cryptoLTC.png\",\n  },\n  {\n    name: \"cryptobch\",\n    short_names: [\"CryptoBCH\"],\n    keywords: [\"crypto\", \"bch\", \"bitcoin\", \"bitcoincash\"],\n    imageUrl: \"/emotes/cryptoBCH.png\",\n  },\n  {\n    name: \"cryptodoge\",\n    short_names: [\"CryptoDOGE\"],\n    keywords: [\"crypto\", \"doge\", \"dogecoin\", \"bestcoin\"],\n    imageUrl: \"/emotes/cryptoDOGE.png\",\n  },\n  {\n    name: \"cryptosushi\",\n    short_names: [\"CryptoSUSHI\"],\n    keywords: [\"crypto\", \"sushi\", \"swap\", \"sushiswap\"],\n    imageUrl: \"/emotes/cryptoSUSHI.png\",\n  },\n  {\n    name: \"cryptotron\",\n    short_names: [\"CryptoTRON\"],\n    keywords: [\"crypto\", \"tron\"],\n    imageUrl: \"/emotes/cryptoTron.png\",\n  },\n  {\n    name: \"cryptozec\",\n    short_names: [\"CryptoZEC\"],\n    keywords: [\"crypto\", \"zec\", \"zcash\"],\n    imageUrl: \"/emotes/cryptoZEC.png\",\n  },\n  {\n    name: \"cryptoetc\",\n    short_names: [\"CryptoETC\"],\n    keywords: [\"crypto\", \"etc\", \"ethereum\", \"ethereumclassic\"],\n    imageUrl: \"/emotes/cryptoETC.png\",\n  },\n  {\n    name: \"cryptocake\",\n    short_names: [\"CryptoCAKE\"],\n    keywords: [\"crypto\", \"cake\", \"swap\", \"pancakeswap\"],\n    imageUrl: \"/emotes/cryptoCAKE.png\",\n  },\n  {\n    name: \"cryptoada\",\n    short_names: [\"CryptoADA\"],\n    keywords: [\"crypto\", \"ada\", \"cardano\"],\n    imageUrl: \"/emotes/cryptoADA.png\",\n  },\n  {\n    name: \"cryptoxrp\",\n    short_names: [\"CryptoXRP\"],\n    keywords: [\"crypto\", \"xrp\", \"ripple\"],\n    imageUrl: \"/emotes/cryptoXRP.png\",\n  },\n  {\n    name: \"cryptousdc\",\n    short_names: [\"CryptoUSDC\"],\n    keywords: [\"crypto\", \"usdc\", \"usdcoin\"],\n    imageUrl: \"/emotes/cryptoUSDC.png\",\n  },\n  {\n    name: \"dodgycoin\",\n    short_names: [\"DodgyCoin\"],\n    keywords: [\"crypto\", \"doge\", \"dodgycoin\"],\n    imageUrl: \"/emotes/dodgyCoin.png\",\n  },\n  {\n    name: \"pepebckl\",\n    short_names: [\"pepeBCKL\"],\n    keywords: [\n      \"pepebckl\",\n      \"bckl\",\n      \"pepe\",\n      \"malarkey\",\n      \"jesse\",\n      \"penguin\",\n      \"stallman\",\n      \"freesoftware\",\n      \"fsf\",\n      \"charlie\",\n      \"kernel\",\n      \"lab\",\n      \"kernellab\",\n      \"computing\",\n      \"jessecharlie\",\n      \"linux\",\n      \"torvalds\",\n    ],\n    imageUrl: \"/emotes/pepeBCKL.png\",\n  },\n  {\n    name: \"5head\",\n    short_names: [\"5Head\"],\n    keywords: [\"5head\", \"big\", \"brain\", \"bigbrain\", \"smart\"],\n    imageUrl: \"/emotes/5Head.png\",\n  },\n  {\n    name: \"ayaya\",\n    short_names: [\"AYAYA\"],\n    keywords: [\"ayaya\"],\n    imageUrl: \"/emotes/AYAYA.png\",\n  },\n  {\n    name: \"babayep\",\n    short_names: [\"babaYEP\"],\n    keywords: [\"babayep\", \"baba\", \"yep\"],\n    imageUrl: \"/emotes/babaYEP.png\",\n  },\n  {\n    name: \"bboomer\",\n    short_names: [\"BBoomer\"],\n    keywords: [\"bboomer\", \"boomer\"],\n    imageUrl: \"/emotes/BBoomer.gif\",\n  },\n  {\n    name: \"bebela\",\n    short_names: [\"BebeLa\"],\n    keywords: [\"bebela\", \"bebe\"],\n    imageUrl: \"/emotes/BebeLa.png\",\n  },\n  {\n    name: \"bern\",\n    short_names: [\"BERN\"],\n    keywords: [\n      \"bern\",\n      \"notmeus\",\n      \"bernie\",\n      \"sanders\",\n      \"berniesanders\",\n      \"bernard\",\n      \"socialism\",\n      \"socialist\",\n      \"comrade\",\n      \"democrat\",\n      \"malarkey\",\n      \"biden\",\n      \"joe\",\n      \"joebiden\",\n      \"us\",\n      \"usa\",\n      \"america\",\n    ],\n    imageUrl: \"/emotes/BERN.png\",\n  },\n  {\n    name: \"bidenjam\",\n    short_names: [\"bidenJAM\"],\n    keywords: [\n      \"bidenjam\",\n      \"jam\",\n      \"jamjam\",\n      \"malarkey\",\n      \"ridinwithbiden\",\n      \"icecream\",\n      \"joe\",\n      \"biden\",\n      \"joebiden\",\n      \"46\",\n      \"president\",\n      \"laser\",\n      \"potus\",\n      \"democrat\",\n      \"us\",\n      \"usa\",\n      \"america\",\n    ],\n    imageUrl: \"/emotes/bidenJAM.gif\",\n  },\n  {\n    name: \"playa\",\n    short_names: [\"Playa\"],\n    keywords: [\"Playa\"],\n    imageUrl: \"/emotes/Playa.png\",\n  },\n  {\n    name: \"igetit\",\n    short_names: [\"IGetIt\"],\n    keywords: [\"IGetIt\", \"Smile\", \"PepeLa\", \"Smug\"],\n    imageUrl: \"/emotes/IGetIt.png\",\n  },\n  {\n    name: \"blankies\",\n    short_names: [\"BLANKIES\"],\n    keywords: [\"blankies\", \"jammies\", \"blanket\", \"dance\"],\n    imageUrl: \"/emotes/BLANKIES.gif\",\n  },\n  {\n    name: \"bogged\",\n    short_names: [\"BOGGED\"],\n    keywords: [\n      \"bogged\",\n      \"bog\",\n      \"hello\",\n      \"phone\",\n      \"iphone\",\n      \"calling\",\n      \"call\",\n      \"phonecall\",\n    ],\n    imageUrl: \"/emotes/BOGGED.png\",\n  },\n  {\n    name: \"clap\",\n    short_names: [\"Clap\"],\n    keywords: [\"clap\", \"clapping\"],\n    imageUrl: \"/emotes/Clap.gif\",\n  },\n  {\n    name: \"coggers\",\n    short_names: [\"COGGERS\"],\n    keywords: [\"coggers\", \"pog\", \"pogu\", \"poggies\", \"poggers\", \"pogger\"],\n    imageUrl: \"/emotes/COGGERS.gif\",\n  },\n  {\n    name: \"copium\",\n    short_names: [\"COPIUM\"],\n    keywords: [\"copium\", \"cope\"],\n    imageUrl: \"/emotes/COPIUM.png\",\n  },\n  {\n    name: \"coronas\",\n    short_names: [\"coronaS\"],\n    keywords: [\"coronas\", \"corona\", \"mask\", \"covid\"],\n    imageUrl: \"/emotes/coronaS.png\",\n  },\n  {\n    name: \"crabpls\",\n    short_names: [\"CrabPls\"],\n    keywords: [\"crabpls\", \"crab\", \"pls\", \"plz\", \"please\"],\n    imageUrl: \"/emotes/crabpls.gif\",\n  },\n  {\n    name: \"dankhackermans\",\n    short_names: [\"DANKHACKERMANS\"],\n    keywords: [\n      \"dankhackermans\",\n      \"dank\",\n      \"hacker\",\n      \"hackermans\",\n      \"keyboard\",\n      \"typing\",\n      \"computer\",\n      \"coding\",\n      \"coder\",\n      \"code\",\n      \"virus\",\n      \"malware\",\n    ],\n    imageUrl: \"/emotes/DANKHACKERMANS.gif\",\n  },\n  {\n    name: \"dankhug\",\n    short_names: [\"dankHug\"],\n    keywords: [\"dankhug\", \"dank\", \"hug\"],\n    imageUrl: \"/emotes/dankHug.png\",\n  },\n  {\n    name: \"dankies\",\n    short_names: [\"DANKIES\"],\n    keywords: [\"dankies\", \"dank\"],\n    imageUrl: \"/emotes/DANKIES.gif\",\n  },\n  {\n    name: \"dewtime\",\n    short_names: [\"DewTime\"],\n    keywords: [\"dewtime\", \"mountaindew\", \"dew\", \"moutain\", \"time\"],\n    imageUrl: \"/emotes/DewTime.gif\",\n  },\n  {\n    name: \"ez\",\n    short_names: [\"EZ\"],\n    keywords: [\"ez\"],\n    imageUrl: \"/emotes/EZ.png\",\n  },\n  { name: \"f\", short_names: [\"F\"], keywords: [\"f\"], imageUrl: \"/emotes/F.gif\" },\n  {\n    name: \"feelsdankman\",\n    short_names: [\"FeelsDankMan\"],\n    keywords: [\"feelsdankman\", \"feels\", \"feel\", \"dank\", \"man\"],\n    imageUrl: \"/emotes/FeelsDankMan.png\",\n  },\n  {\n    name: \"feelsokayman\",\n    short_names: [\"FeelsOkayMan\"],\n    keywords: [\"feelsokayman\", \"feels\", \"feel\", \"okay\", \"man\"],\n    imageUrl: \"/emotes/FeelsOkayMan.png\",\n  },\n  {\n    name: \"feelsstrongman\",\n    short_names: [\"FeelsStrongMan\"],\n    keywords: [\"feelsstrongman\", \"feels\", \"feel\", \"strong\", \"man\"],\n    imageUrl: \"/emotes/FeelsStrongMan.png\",\n  },\n  {\n    name: \"feelsweirdman\",\n    short_names: [\"FeelsWeirdMan\"],\n    keywords: [\"feelsweirdman\", \"feels\", \"feel\", \"weird\", \"man\"],\n    imageUrl: \"/emotes/FeelsWeirdMan.png\",\n  },\n  {\n    name: \"gachihyper\",\n    short_names: [\"gachiHYPER\"],\n    keywords: [\"gachihyper\", \"gachi\", \"hyper\", \"coomer\"],\n    imageUrl: \"/emotes/gachiHYPER.gif\",\n  },\n  {\n    name: \"grug\",\n    short_names: [\"GRUG\"],\n    keywords: [\"grug\"],\n    imageUrl: \"/emotes/GRUG.png\",\n  },\n  {\n    name: \"guitartime\",\n    short_names: [\"GuitarTime\"],\n    keywords: [\"guitartime\", \"guitar\", \"time\"],\n    imageUrl: \"/emotes/GuitarTime.gif\",\n  },\n  {\n    name: \"hackermans\",\n    short_names: [\"HACKERMANS\"],\n    keywords: [\n      \"hackermans\",\n      \"hacker\",\n      \"keyboard\",\n      \"typing\",\n      \"computer\",\n      \"coding\",\n      \"coder\",\n      \"code\",\n      \"virus\",\n      \"malware\",\n    ],\n    imageUrl: \"/emotes/HACKERMANS.gif\",\n  },\n  {\n    name: \"handsup\",\n    short_names: [\"HandsUp\"],\n    keywords: [\"handsup\", \"hands\", \"up\", \"hand\"],\n    imageUrl: \"/emotes/HandsUp.png\",\n  },\n  {\n    name: \"hasanhypersmash\",\n    short_names: [\"hasanHyperSmash\"],\n    keywords: [\n      \"hasanhypersmash\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"hyper\",\n      \"smash\",\n    ],\n    imageUrl: \"/emotes/hasanHyperSmash.gif\",\n  },\n  {\n    name: \"hasansmash\",\n    short_names: [\"hasanSmash\"],\n    keywords: [\"hasansmash\", \"hasan\", \"hasanabi\", \"hasanpiker\", \"smash\"],\n    imageUrl: \"/emotes/hasanSmash.gif\",\n  },\n  {\n    name: \"hasanwalk\",\n    short_names: [\"HasanWalk\"],\n    keywords: [\n      \"hasanwalk\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"walk\",\n      \"walking\",\n    ],\n    imageUrl: \"/emotes/HasanWalk.gif\",\n  },\n  {\n    name: \"hashyperjam\",\n    short_names: [\"hasHyperJAM\"],\n    keywords: [\n      \"hashyperjam\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"hyper\",\n      \"jam\",\n      \"jamjam\",\n      \"jammies\",\n    ],\n    imageUrl: \"/emotes/hasHyperJAM.gif\",\n  },\n  {\n    name: \"haspls\",\n    short_names: [\"hasPls\"],\n    keywords: [\n      \"haspls\",\n      \"please\",\n      \"pls\",\n      \"plz\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n    ],\n    imageUrl: \"/emotes/hasPls.gif\",\n  },\n  {\n    name: \"hasrock\",\n    short_names: [\"hasRock\"],\n    keywords: [\n      \"hasrock\",\n      \"spongebob\",\n      \"patrick\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"rock\",\n    ],\n    imageUrl: \"/emotes/hasRock.gif\",\n  },\n  {\n    name: \"hastasty\",\n    short_names: [\"hasTasty\"],\n    keywords: [\n      \"hastasty\",\n      \"hasan\",\n      \"tasty\",\n      \"yum\",\n      \"yummy\",\n      \"hasanabi\",\n      \"hasanpiker\",\n    ],\n    imageUrl: \"/emotes/hasTasty.gif\",\n  },\n  {\n    name: \"hyperclap\",\n    short_names: [\"HYPERCLAP\"],\n    keywords: [\"hyperclap\", \"hyper\", \"clap\"],\n    imageUrl: \"/emotes/HYPERCLAP.gif\",\n  },\n  {\n    name: \"hyperhammer\",\n    short_names: [\"hyperHammer\"],\n    keywords: [\"hyperhammer\", \"hyper\", \"hammer\"],\n    imageUrl: \"/emotes/hyperHammer.gif\",\n  },\n  {\n    name: \"hyperitalianhands\",\n    short_names: [\"HYPERITALIANHANDS\"],\n    keywords: [\n      \"hyperitalianhands\",\n      \"hyper\",\n      \"italian\",\n      \"hands\",\n      \"hand\",\n      \"italy\",\n      \"ovahere\",\n    ],\n    imageUrl: \"/emotes/HYPERITALIANHANDS.gif\",\n  },\n  {\n    name: \"hyperpogger\",\n    short_names: [\"HYPERPOGGER\"],\n    keywords: [\"hyperpogger\", \"hyper\", \"pog\", \"pogger\", \"poggies\"],\n    imageUrl: \"/emotes/HYPERPOGGER.gif\",\n  },\n  {\n    name: \"hyperpogo\",\n    short_names: [\"HYPERPOGO\"],\n    keywords: [\n      \"hyperpogo\",\n      \"hyper\",\n      \"pog\",\n      \"pogo\",\n      \"azan\",\n      \"poggies\",\n      \"poggers\",\n      \"pogger\",\n    ],\n    imageUrl: \"/emotes/HYPERPOGO.gif\",\n  },\n  {\n    name: \"italianhands\",\n    short_names: [\"ItalianHands\"],\n    keywords: [\"italianhands\", \"ovahere\", \"italian\", \"hands\", \"hand\", \"italy\"],\n    imageUrl: \"/emotes/ItalianHands.gif\",\n  },\n  {\n    name: \"jammies\",\n    short_names: [\"Jammies\"],\n    keywords: [\"jammies\", \"jam\", \"jamjam\", \"dance\", \"vibe\"],\n    imageUrl: \"/emotes/Jammies.gif\",\n  },\n  {\n    name: \"kekw\",\n    short_names: [\"KEKW\"],\n    keywords: [\n      \"kekw\",\n      \"kek\",\n      \"omegalul\",\n      \"lol\",\n      \"lmao\",\n      \"haha\",\n      \"ha\",\n      \"ja\",\n      \"jaja\",\n    ],\n    imageUrl: \"/emotes/KEKW.png\",\n  },\n  {\n    name: \"kekwait\",\n    short_names: [\"KEKWait\"],\n    keywords: [\n      \"kekwait\",\n      \"lul\",\n      \"omegalul\",\n      \"lol\",\n      \"lmao\",\n      \"wait\",\n      \"kek\",\n      \"ha\",\n    ],\n    imageUrl: \"/emotes/KEKWait.png\",\n  },\n  {\n    name: \"kkapitalist\",\n    short_names: [\"KKapitalist\"],\n    keywords: [\"kkapitalist\", \"kapitalist\", \"capitalist\", \"business\"],\n    imageUrl: \"/emotes/KKapitalist.png\",\n  },\n  {\n    name: \"kkomrade\",\n    short_names: [\"KKomrade\"],\n    keywords: [\"kkomrade\", \"komrade\", \"comrade\"],\n    imageUrl: \"/emotes/KKomrade.png\",\n  },\n  {\n    name: \"kkonaw\",\n    short_names: [\"KKonaW\"],\n    keywords: [\"kkonaw\"],\n    imageUrl: \"/emotes/KKonaW.png\",\n  },\n  {\n    name: \"kkop\",\n    short_names: [\"KKop\"],\n    keywords: [\n      \"kkop\",\n      \"kop\",\n      \"kops\",\n      \"cop\",\n      \"cops\",\n      \"police\",\n      \"acab\",\n      \"blueline\",\n    ],\n    imageUrl: \"/emotes/KKop.png\",\n  },\n  {\n    name: \"lulw\",\n    short_names: [\"LULW\"],\n    keywords: [\"lulw\", \"lul\", \"lol\", \"lmao\", \"omegalul\"],\n    imageUrl: \"/emotes/LULW.png\",\n  },\n  {\n    name: \"lulwut\",\n    short_names: [\"lulWut\"],\n    keywords: [\n      \"lulwut\",\n      \"lul\",\n      \"lmao\",\n      \"lol\",\n      \"wut\",\n      \"what\",\n      \"wat\",\n      \"wtf\",\n      \"omegalul\",\n      \"ha\",\n      \"haha\",\n      \"ja\",\n      \"jaja\",\n    ],\n    imageUrl: \"/emotes/lulWut.png\",\n  },\n  {\n    name: \"malarkey\",\n    short_names: [\"MALARKEY\"],\n    keywords: [\n      \"malarkey\",\n      \"ridinwithbiden\",\n      \"icecream\",\n      \"joe\",\n      \"biden\",\n      \"joebiden\",\n      \"46\",\n      \"president\",\n      \"laser\",\n      \"potus\",\n      \"democrat\",\n      \"us\",\n      \"usa\",\n      \"america\",\n    ],\n    imageUrl: \"/emotes/MALARKEY.gif\",\n  },\n  {\n    name: \"mmmhmm\",\n    short_names: [\"MmmHmm\"],\n    keywords: [\n      \"mmmhmm\",\n      \"mmm\",\n      \"mmmm\",\n      \"hmm\",\n      \"hmmm\",\n      \"mmmmhmmm\",\n      \"mmmmhmm\",\n      \"mmmhmmm\",\n    ],\n    imageUrl: \"/emotes/MmmHmm.gif\",\n  },\n  {\n    name: \"modcheck\",\n    short_names: [\"modCheck\"],\n    keywords: [\n      \"modcheck\",\n      \"mods\",\n      \"mod\",\n      \"check\",\n      \"spongebob\",\n      \"ask\",\n      \"askers\",\n      \"whoasked\",\n      \"where\",\n      \"look\",\n      \"fish\",\n      \"oooo\",\n    ],\n    imageUrl: \"/emotes/modCheck.gif\",\n  },\n  {\n    name: \"modtime\",\n    short_names: [\"ModTime\"],\n    keywords: [\"modtime\", \"mod\", \"mods\", \"time\"],\n    imageUrl: \"/emotes/ModTime.gif\",\n  },\n  {\n    name: \"monkahmm\",\n    short_names: [\"monkaHmm\"],\n    keywords: [\"monkahmm\", \"monka\", \"hmmm\", \"hmm\"],\n    imageUrl: \"/emotes/monkaHmm.png\",\n  },\n  {\n    name: \"monkastare\",\n    short_names: [\"monkaStare\"],\n    keywords: [\"monkastare\", \"stare\", \"monka\"],\n    imageUrl: \"/emotes/monkaStare.png\",\n  },\n  {\n    name: \"monkasteer\",\n    short_names: [\"monkaSTEER\"],\n    keywords: [\"monkasteer\", \"monka\", \"steer\", \"ridinwithbiden\"],\n    imageUrl: \"/emotes/monkaSTEER.gif\",\n  },\n  {\n    name: \"monkaw\",\n    short_names: [\"monkaW\"],\n    keywords: [\"monkaw\", \"monka\"],\n    imageUrl: \"/emotes/monkaW.png\",\n  },\n  {\n    name: \"neffhyperjam\",\n    short_names: [\"neffHyperJAM\"],\n    keywords: [\n      \"neffhyperjam\",\n      \"hyperjam\",\n      \"jam\",\n      \"jammies\",\n      \"boppin\",\n      \"dj\",\n      \"jamjam\",\n      \"neff\",\n      \"will\",\n      \"willneff\",\n    ],\n    imageUrl: \"/emotes/neffHyperJAM.gif\",\n  },\n  {\n    name: \"nodders\",\n    short_names: [\"NODDERS\"],\n    keywords: [\"nodders\", \"nod\", \"noddies\"],\n    imageUrl: \"/emotes/NODDERS.gif\",\n  },\n  {\n    name: \"nopers\",\n    short_names: [\"NOPERS\"],\n    keywords: [\"nopers\", \"no\", \"nope\"],\n    imageUrl: \"/emotes/NOPERS.gif\",\n  },\n  {\n    name: \"nymncorn\",\n    short_names: [\"nymnCorn\"],\n    keywords: [\"nymncorn\", \"mmm\", \"corn\", \"nymn\"],\n    imageUrl: \"/emotes/nymnCorn.gif\",\n  },\n  {\n    name: \"okaychamp\",\n    short_names: [\"OkayChamp\"],\n    keywords: [\"okaychamp\", \"okay\", \"champ\"],\n    imageUrl: \"/emotes/OkayChamp.png\",\n  },\n  {\n    name: \"omegalul\",\n    short_names: [\"OMEGALUL\"],\n    keywords: [\"omegalul\", \"lol\", \"lmao\", \"lul\", \"ha\", \"haha\", \"ja\", \"jaja\"],\n    imageUrl: \"/emotes/omegalul.png\",\n  },\n  {\n    name: \"oooo\",\n    short_names: [\"OOOO\"],\n    keywords: [\"oooo\", \"fish\"],\n    imageUrl: \"/emotes/OOOO.gif\",\n  },\n  {\n    name: \"pagchomp\",\n    short_names: [\"PagChomp\"],\n    keywords: [\"pagchomp\", \"pag\", \"chomp\"],\n    imageUrl: \"/emotes/PagChomp.png\",\n  },\n  {\n    name: \"painschamp\",\n    short_names: [\"PainsChamp\"],\n    keywords: [\"painschamp\", \"pains\", \"champ\", \"pain\"],\n    imageUrl: \"/emotes/PainsChamp.png\",\n  },\n  {\n    name: \"pausechamp\",\n    short_names: [\"PauseChamp\"],\n    keywords: [\"pausechamp\", \"pause\", \"champ\"],\n    imageUrl: \"/emotes/PauseChamp.png\",\n  },\n  {\n    name: \"peepoarrive\",\n    short_names: [\"peepoArrive\"],\n    keywords: [\n      \"peepoarrive\",\n      \"peepo\",\n      \"pepo\",\n      \"arrive\",\n      \"here\",\n      \"welcome\",\n      \"hey\",\n      \"wave\",\n      \"hi\",\n      \"hello\",\n      \"howdy\",\n    ],\n    imageUrl: \"/emotes/peepoArrive.gif\",\n  },\n  {\n    name: \"peepobaba\",\n    short_names: [\"peepoBaba\"],\n    keywords: [\"peepobaba\", \"peepo\", \"pepo\", \"baba\"],\n    imageUrl: \"/emotes/peepoBaba.png\",\n  },\n  {\n    name: \"peepobye\",\n    short_names: [\"peepoBye\"],\n    keywords: [\"peepobye\", \"peepo\", \"pepo\", \"bye\"],\n    imageUrl: \"/emotes/peepoBye.gif\",\n  },\n  {\n    name: \"peepochat\",\n    short_names: [\"peepoChat\"],\n    keywords: [\"peepochat\", \"peepo\", \"pepo\", \"chat\"],\n    imageUrl: \"/emotes/peepoChat.gif\",\n  },\n  {\n    name: \"peepocheer\",\n    short_names: [\"peepoCheer\"],\n    keywords: [\"peepocheer\", \"peepo\", \"pepo\", \"cheer\"],\n    imageUrl: \"/emotes/peepoCheer.gif\",\n  },\n  {\n    name: \"peepoclap\",\n    short_names: [\"peepoClap\"],\n    keywords: [\"peepoclap\", \"peepo\", \"pepo\", \"clap\"],\n    imageUrl: \"/emotes/peepoClap.gif\",\n  },\n  {\n    name: \"peepod\",\n    short_names: [\"peepoD\"],\n    keywords: [\"peepod\", \"peepo\", \"pepo\"],\n    imageUrl: \"/emotes/peepoD.gif\",\n  },\n  {\n    name: \"peepofat\",\n    short_names: [\"peepoFat\"],\n    keywords: [\"peepofat\", \"peepo\", \"pepo\", \"fat\", \"houngry\", \"hungry\", \"food\"],\n    imageUrl: \"/emotes/peepoFat.png\",\n  },\n  {\n    name: \"peepogiggles\",\n    short_names: [\"peepoGiggles\"],\n    keywords: [\"peepogiggles\", \"pepo\", \"peepo\", \"giggle\", \"giggles\"],\n    imageUrl: \"/emotes/peepoGiggles.gif\",\n  },\n  {\n    name: \"peepohey\",\n    short_names: [\"peepoHey\"],\n    keywords: [\n      \"peepohey\",\n      \"peepo\",\n      \"wave\",\n      \"pepo\",\n      \"hey\",\n      \"hi\",\n      \"hello\",\n      \"howdy\",\n      \"welcome\",\n    ],\n    imageUrl: \"/emotes/peepoHey.gif\",\n  },\n  {\n    name: \"peepojammer\",\n    short_names: [\"peepoJAMMER\"],\n    keywords: [\"peepojammer\", \"peepo\", \"pepo\", \"jam\", \"jammer\", \"jamjam\"],\n    imageUrl: \"/emotes/peepoJAMMER.gif\",\n  },\n  {\n    name: \"peepokiss\",\n    short_names: [\"peepoKiss\"],\n    keywords: [\"peepokiss\", \"peepo\", \"pepo\", \"kiss\", \"love\"],\n    imageUrl: \"/emotes/peepoKiss.png\",\n  },\n  {\n    name: \"peepoleave\",\n    short_names: [\"peepoLeave\"],\n    keywords: [\"peepoleave\", \"peepo\", \"pepo\", \"leave\", \"exit\", \"bye\"],\n    imageUrl: \"/emotes/peepoLeave.gif\",\n  },\n  {\n    name: \"peepopog\",\n    short_names: [\"peepoPog\"],\n    keywords: [\"peepopog\", \"peepo\", \"pepo\", \"pog\"],\n    imageUrl: \"/emotes/peepoPog.png\",\n  },\n  {\n    name: \"peepopogo\",\n    short_names: [\"peepoPogO\"],\n    keywords: [\"peepopogo\", \"peepo\", \"pepo\", \"pog\", \"pogo\"],\n    imageUrl: \"/emotes/peepoPogO.png\",\n  },\n  {\n    name: \"peeporun\",\n    short_names: [\"peepoRun\"],\n    keywords: [\"peeporun\", \"peepo\", \"pepo\", \"run\"],\n    imageUrl: \"/emotes/peepoRun.gif\",\n  },\n  {\n    name: \"peeposhy\",\n    short_names: [\"peepoShy\"],\n    keywords: [\"peeposhy\", \"peepo\", \"pepo\", \"shy\"],\n    imageUrl: \"/emotes/peepoShy.gif\",\n  },\n  {\n    name: \"peeposnow\",\n    short_names: [\"peepoSnow\"],\n    keywords: [\n      \"peeposnow\",\n      \"peepo\",\n      \"pepo\",\n      \"snow\",\n      \"christmas\",\n      \"cold\",\n      \"holidays\",\n    ],\n    imageUrl: \"/emotes/peepoSnow.gif\",\n  },\n  {\n    name: \"peepot\",\n    short_names: [\"peepoT\"],\n    keywords: [\"peepot\", \"peepo\", \"pepo\"],\n    imageUrl: \"/emotes/peepoT.gif\",\n  },\n  {\n    name: \"peepoweird\",\n    short_names: [\"peepoWeird\"],\n    keywords: [\"peepoweird\", \"peepo\", \"pepo\", \"weird\"],\n    imageUrl: \"/emotes/peepoWeird.png\",\n  },\n  {\n    name: \"pepecd\",\n    short_names: [\"pepeCD\"],\n    keywords: [\"pepecd\", \"pepe\", \"cd\"],\n    imageUrl: \"/emotes/pepeCD.gif\",\n  },\n  {\n    name: \"pepegehmm\",\n    short_names: [\"PepegeHmm\"],\n    keywords: [\"pepegehmm\", \"pepege\", \"pepe\", \"hmm\", \"hmmm\"],\n    imageUrl: \"/emotes/PepegeHmm.png\",\n  },\n  {\n    name: \"pepehands\",\n    short_names: [\"PepeHands\"],\n    keywords: [\"pepehands\", \"pepe\", \"hands\"],\n    imageUrl: \"/emotes/PepeHands.png\",\n  },\n  {\n    name: \"pepejamjam\",\n    short_names: [\"pepeJAMJAM\"],\n    keywords: [\"pepejamjam\", \"pepe\", \"jam\"],\n    imageUrl: \"/emotes/pepeJAMJAM.gif\",\n  },\n  {\n    name: \"pepejam\",\n    short_names: [\"pepeJAM\"],\n    keywords: [\"pepeJAM\", \"pepe\"],\n    imageUrl: \"/emotes/pepejam.gif\",\n  },\n  {\n    name: \"pepela\",\n    short_names: [\"PepeLa\"],\n    keywords: [\"pepela\", \"pepe\"],\n    imageUrl: \"/emotes/PepeLa.png\",\n  },\n  {\n    name: \"pepelaugh\",\n    short_names: [\"PepeLaugh\"],\n    keywords: [\"pepelaugh\", \"pepe\", \"laugh\"],\n    imageUrl: \"/emotes/PepeLaugh.gif\",\n  },\n  {\n    name: \"pepemeltdown\",\n    short_names: [\"pepeMeltdown\"],\n    keywords: [\"pepemeltdown\", \"pepe\", \"meltdown\"],\n    imageUrl: \"/emotes/pepeMeltdown.gif\",\n  },\n  {\n    name: \"pepep\",\n    short_names: [\"pepeP\"],\n    keywords: [\"pepep\", \"pepe\"],\n    imageUrl: \"/emotes/pepeP.gif\",\n  },\n  {\n    name: \"pepes\",\n    short_names: [\"PepeS\"],\n    keywords: [\"pepes\", \"pepe\"],\n    imageUrl: \"/emotes/PepeS.gif\",\n  },\n  {\n    name: \"pepespit\",\n    short_names: [\"PepeSpit\"],\n    keywords: [\"pepespit\", \"pepe\", \"spit\"],\n    imageUrl: \"/emotes/PepeSpit.gif\",\n  },\n  {\n    name: \"pepew\",\n    short_names: [\"pepeW\"],\n    keywords: [\"pepew\", \"pepe\"],\n    imageUrl: \"/emotes/pepeW.gif\",\n  },\n  {\n    name: \"pepog\",\n    short_names: [\"PepoG\"],\n    keywords: [\"pepog\", \"pepo\", \"peepo\"],\n    imageUrl: \"/emotes/PepoG.png\",\n  },\n  {\n    name: \"pog\",\n    short_names: [\"Pog\"],\n    keywords: [\"pog\", \"poggies\", \"pogger\", \"poggers\"],\n    imageUrl: \"/emotes/Pog.png\",\n  },\n  {\n    name: \"poggers\",\n    short_names: [\"POGGERS\"],\n    keywords: [\"poggers\", \"pog\", \"poggies\", \"pogger\"],\n    imageUrl: \"/emotes/POGGERS.png\",\n  },\n  {\n    name: \"poggies\",\n    short_names: [\"POGGIES\"],\n    keywords: [\"poggies\", \"pog\", \"pogger\"],\n    imageUrl: \"/emotes/POGGIES.png\",\n  },\n  {\n    name: \"pogo\",\n    short_names: [\"PogO\"],\n    keywords: [\"pogo\", \"pog\", \"azan\"],\n    imageUrl: \"/emotes/PogO.png\",\n  },\n  {\n    name: \"pogu\",\n    short_names: [\"PogU\"],\n    keywords: [\"pogu\", \"pog\"],\n    imageUrl: \"/emotes/PogU.png\",\n  },\n  {\n    name: \"pphop\",\n    short_names: [\"ppHop\"],\n    keywords: [\"pphop\", \"hop\"],\n    imageUrl: \"/emotes/ppHop.gif\",\n  },\n  {\n    name: \"ppoverheat\",\n    short_names: [\"ppOverheat\"],\n    keywords: [\"ppoverheat\", \"overheat\"],\n    imageUrl: \"/emotes/ppOverheat.gif\",\n  },\n  {\n    name: \"pppoof\",\n    short_names: [\"ppPoof\"],\n    keywords: [\"pppoof\", \"poof\"],\n    imageUrl: \"/emotes/ppPoof.gif\",\n  },\n  {\n    name: \"prayge\",\n    short_names: [\"Prayge\"],\n    keywords: [\"prayge\", \"pray\", \"god\"],\n    imageUrl: \"/emotes/Prayge.png\",\n  },\n  {\n    name: \"ratjam\",\n    short_names: [\"ratJAM\"],\n    keywords: [\"ratjam\", \"rat\", \"jam\"],\n    imageUrl: \"/emotes/ratJAM.gif\",\n  },\n  {\n    name: \"ree\",\n    short_names: [\"REE\"],\n    keywords: [\"ree\", \"reeee\"],\n    imageUrl: \"/emotes/REE.png\",\n  },\n  {\n    name: \"sillychamp\",\n    short_names: [\"SillyChamp\"],\n    keywords: [\"sillychamp\", \"silly\", \"champ\"],\n    imageUrl: \"/emotes/SillyChamp.png\",\n  },\n  {\n    name: \"smoketime\",\n    short_names: [\"SmokeTime\"],\n    keywords: [\"smoketime\", \"smoke\"],\n    imageUrl: \"/emotes/SmokeTime.gif\",\n  },\n  {\n    name: \"swooner\",\n    short_names: [\"SWOONER\"],\n    keywords: [\"swooner\"],\n    imageUrl: \"/emotes/SWOONER.png\",\n  },\n  {\n    name: \"teatime\",\n    short_names: [\"TeaTime\"],\n    keywords: [\"teatime\", \"tea\"],\n    imageUrl: \"/emotes/TeaTime.gif\",\n  },\n  {\n    name: \"tomatotime\",\n    short_names: [\"TomatoTime\"],\n    keywords: [\"tomatotime\", \"tomato\"],\n    imageUrl: \"/emotes/TomatoTime.gif\",\n  },\n  {\n    name: \"unpoggers\",\n    short_names: [\"unPOGGERS\"],\n    keywords: [\"unpoggers\", \"poggers\", \"pog\", \"unpoggies\"],\n    imageUrl: \"/emotes/unPOGGERS.png\",\n  },\n  {\n    name: \"weirdchamp\",\n    short_names: [\"WeirdChamp\"],\n    keywords: [\"weirdchamp\", \"weird\", \"champ\"],\n    imageUrl: \"/emotes/WeirdChamp.png\",\n  },\n  {\n    name: \"weirdge\",\n    short_names: [\"Weirdge\"],\n    keywords: [\"weirdge\", \"weird\"],\n    imageUrl: \"/emotes/Weirdge.png\",\n  },\n  {\n    name: \"whatchamp\",\n    short_names: [\"WhatChamp\"],\n    keywords: [\"whatchamp\", \"what\", \"champ\"],\n    imageUrl: \"/emotes/WhatChamp.png\",\n  },\n  {\n    name: \"wicked\",\n    short_names: [\"WICKED\"],\n    keywords: [\"wicked\"],\n    imageUrl: \"/emotes/WICKED.png\",\n  },\n  {\n    name: \"widepeepohappy\",\n    short_names: [\"widepeepoHappy\"],\n    keywords: [\"widepeepohappy\", \"peepohappy\", \"peepo\", \"happy\"],\n    imageUrl: \"/emotes/widepeepoHappy.png\",\n  },\n  {\n    name: \"widepeeposad\",\n    short_names: [\"widepeepoSad\"],\n    keywords: [\"widepeeposad\", \"peeposad\", \"peepo\", \"sad\"],\n    imageUrl: \"/emotes/widepeepoSad.png\",\n  },\n  {\n    name: \"winetime\",\n    short_names: [\"WineTime\"],\n    keywords: [\"winetime\", \"wine\"],\n    imageUrl: \"/emotes/WineTime.gif\",\n  },\n  {\n    name: \"yep\",\n    short_names: [\"YEP\"],\n    keywords: [\"yep\"],\n    imageUrl: \"/emotes/YEP.png\",\n  },\n  {\n    name: \"zzoomer\",\n    short_names: [\"ZZoomer\"],\n    keywords: [\"zzoomer\", \"zoomer\"],\n    imageUrl: \"/emotes/ZZoomer.gif\",\n  },\n  {\n    name: \"doge3d\",\n    short_names: [\"Doge3D\"],\n    keywords: [\"doge3d\", \"doge\", \"3d\"],\n    imageUrl: \"/emotes/doge3d.gif\",\n  },\n  {\n    name: \"dogecool\",\n    short_names: [\"DogeCool\"],\n    keywords: [\"dogecool\", \"doge\", \"cool\"],\n    imageUrl: \"/emotes/dogecool.gif\",\n  },\n  {\n    name: \"thugpepe\",\n    short_names: [\"ThugPepe\"],\n    keywords: [\"dogehouse\", \"doge\", \"thugpepe\"],\n    imageUrl: \"/emotes/thugpepe.png\",\n  },\n  {\n    name: \"blaze\",\n    short_names: [\"blaze\"],\n    keywords: [\"blaze\", \"yes\", \"snark\"],\n    imageUrl: \"/emotes/blaze.png\",\n  },\n  {\n    name: \"blobdance\",\n    short_names: [\"blobDance\"],\n    keywords: [\"blobDance\", \"vibe\"],\n    imageUrl: \"/emotes/blobdance.gif\",\n  },\n  {\n    name: \"pepepls\",\n    short_names: [\"PepePls\"],\n    keywords: [\"PepePls\", \"pepe\"],\n    imageUrl: \"/emotes/pepepls.gif\",\n  },\n  {\n    name: \"feelsgoodman\",\n    short_names: [\"FeelsGoodMan\"],\n    keywords: [\"FeelsGoodMan\", \"pepe\"],\n    imageUrl: \"/emotes/feelsgoodman.png\",\n  },\n  {\n    name: \"github\",\n    short_names: [\"GitHub\"],\n    keywords: [\"app\", \"git\", \"github\"],\n    imageUrl: \"/emotes/github.png\",\n  },\n  {\n    name: \"elixir\",\n    short_names: [\"Elixir\"],\n    keywords: [\"app\", \"elixir\"],\n    imageUrl: \"/emotes/elixir.png\",\n  },\n  {\n    name: \"nextjs\",\n    short_names: [\"NextJS\"],\n    keywords: [\"app\", \"next\", \"js\"],\n    imageUrl: \"/emotes/nextjs.png\",\n  },\n  {\n    name: \"react\",\n    short_names: [\"React\"],\n    keywords: [\"app\", \"react\", \"js\"],\n    imageUrl: \"/emotes/react.png\",\n  },\n  {\n    name: \"electron\",\n    short_names: [\"Electron\"],\n    keywords: [\"app\", \"electron\"],\n    imageUrl: \"/emotes/electron.png\",\n  },\n  {\n    name: \"beardguyr\",\n    short_names: [\"BeardGuyR\"],\n    keywords: [\"meme\", \"beard\", \"guy\"],\n    imageUrl: \"/emotes/beardguyright.png\",\n  },\n  {\n    name: \"beardguyl\",\n    short_names: [\"BeardGuyL\"],\n    keywords: [\"meme\", \"beard\", \"guy\"],\n    imageUrl: \"/emotes/beardguyleft.png\",\n  },\n  {\n    name: \"mememan\",\n    short_names: [\"MemeMan\"],\n    keywords: [\"meme\", \"guy\"],\n    imageUrl: \"/emotes/mememan.png\",\n  },\n  {\n    name: \"npc\",\n    short_names: [\"NPC\"],\n    keywords: [\"meme\", \"npc\", \"guy\"],\n    imageUrl: \"/emotes/npc.png\",\n  },\n  {\n    name: \"smudgecat\",\n    short_names: [\"SMudgeCat\"],\n    keywords: [\"meme\", \"cat\"],\n    imageUrl: \"/emotes/smudgecat.png\",\n  },\n  {\n    name: \"stonks\",\n    short_names: [\"Stonks\"],\n    keywords: [\"meme\", \"crypto\", \"guy\"],\n    imageUrl: \"/emotes/stonks.png\",\n  },\n  {\n    name: \"stinks\",\n    short_names: [\"Stinks\"],\n    keywords: [\"meme\", \"crypto\", \"guy\"],\n    imageUrl: \"/emotes/stinks.png\",\n  },\n  {\n    name: \"iwmd\",\n    short_names: [\"IWMD\"],\n    keywords: [\"meme\", \"dude\", \"wednesday\", \"frog\"],\n    imageUrl: \"/emotes/wednesday.png\",\n  },\n  {\n    name: \"presponge\",\n    short_names: [\"PreSponge\"],\n    keywords: [\"meme\", \"spongebob\"],\n    imageUrl: \"/emotes/presponge.png\",\n  },\n  {\n    name: \"spongebob\",\n    short_names: [\"sPonGeBoB\"],\n    keywords: [\"meme\", \"spongebob\"],\n    imageUrl: \"/emotes/sPonGeBoB.png\",\n  },\n  {\n    name: \"peepohappy\",\n    short_names: [\"peepohappy\"],\n    keywords: [\"peepohappy\", \"peepo\", \"pepe\"],\n    imageUrl: \"/emotes/peepohappy.png\",\n  },\n  {\n    name: \"peepohug\",\n    short_names: [\"peepohug\"],\n    keywords: [\"peepohug\", \"peepo\", \"pepe\"],\n    imageUrl: \"/emotes/peepohug.png\",\n  },\n  {\n    name: \"purpledogehouse\",\n    short_names: [\"PurpleDogeHouse\"],\n    keywords: [\"purple\", \"dogehouse\", \"doge\"],\n    imageUrl: \"/emotes/purpledogehouse.png\",\n  },\n  {\n    name: \"widepeepopussy\",\n    short_names: [\"widepeepoPussy\"],\n    keywords: [\"widepeepoPussy\", \"peepo\", \"wide\"],\n    imageUrl: \"/emotes/widepeepoPussy.png\",\n  },\n  {\n    name: \"rareparrot\",\n    short_names: [\"RareParrot\"],\n    keywords: [\"Rare\", \"Parrot\", \"party\"],\n    imageUrl: \"/emotes/rareParrot.gif\",\n  },\n  {\n    name: \"orangedogehouse\",\n    short_names: [\"OrangeDogeHouse\"],\n    keywords: [\"orange\", \"dogehouse\", \"doge\"],\n    imageUrl: \"/emotes/orangedogehouse.png\",\n  },\n  {\n    name: \"cyandogehouse\",\n    short_names: [\"CyanDogeHouse\"],\n    keywords: [\"cyan\", \"dogehouse\", \"doge\"],\n    imageUrl: \"/emotes/cyandogehouse.png\",\n  },\n  {\n    name: \"angrykermit\",\n    short_names: [\"AngryKermit\"],\n    keywords: [\"kermit\", \"angry\", \"ak47\"],\n    imageUrl: \"/emotes/angrykermit.png\",\n  },\n  {\n    name: \"darthkermit\",\n    short_names: [\"DarthKermit\"],\n    keywords: [\"kermit\", \"star\", \"wars\", \"darth\"],\n    imageUrl: \"/emotes/darthkermit.png\",\n  },\n  {\n    name: \"takemymoney\",\n    short_names: [\"TakeMyMoney\"],\n    keywords: [\"take\", \"my\", \"money\", \"shut\", \"up\"],\n    imageUrl: \"/emotes/takemymoney.png\",\n  },\n  {\n    name: \"awyeah\",\n    short_names: [\"awyeah\"],\n    keywords: [\"aw\", \"yeah\", \"awyeah\", \"dancing\"],\n    imageUrl: \"/emotes/awyeah.gif\",\n  },\n  {\n    name: \"ztlul\",\n    short_names: [\"ztlul\", \"zerotwolul\"],\n    keywords: [\"lul\", \"lol\", \"anime\", \"girl\", \"zerotwo\"],\n    imageUrl: \"/emotes/zerotwolul.png\",\n  },\n  {\n    name: \"ztsmug\",\n    short_names: [\"ztsmug\", \"zerotwosmug\"],\n    keywords: [\"smug\", \"anime\", \"girl\", \"zerotwo\"],\n    imageUrl: \"/emotes/zerotwosmug.png\",\n  },\n  {\n    name: \"ztthinking\",\n    short_names: [\"ztthinking\"],\n    keywords: [\n      \"thinking\",\n      \"think\",\n      \"anime\",\n      \"girl\",\n      \"zerotwo\",\n      \"zerotwothinking\",\n    ],\n    imageUrl: \"/emotes/zerotwothinking.png\",\n  },\n  {\n    name: \"partyparrot\",\n    short_names: [\"partyparrot\"],\n    keywords: [\"party\", \"high\", \"parrot\", \"fun\"],\n    imageUrl: \"/emotes/PartyParrot.gif\",\n  },\n  {\n    name: \"dogepls\",\n    short_names: [\"dogepls\"],\n    keywords: [\"doge\", \"please\", \"fun\", \"dance\"],\n    imageUrl: \"/emotes/DogePls.gif\",\n  },\n  {\n    name: \"catdance\",\n    short_names: [\"catDance\"],\n    keywords: [\"cat\", \"dance\", \"party\", \"fun\"],\n    imageUrl: \"/emotes/catDance.gif\",\n  },\n  {\n    name: \"doughdoge\",\n    short_names: [\"doughdoge\"],\n    keywords: [\"dough\", \"doge\", \"pizza\"],\n    imageUrl: \"/emotes/doughdoge.png\",\n  },\n];\n\ntype EmoteList = typeof customEmojis;\nexport type EmoteKeys = EmoteList[number][\"name\"];\n\nexport const emoteMap: Record<string, string> = {};\n\nexport type CustomEmote = {\n  name: string;\n  short_names: string[];\n  keywords: string[];\n  imageUrl: string;\n};\n\ncustomEmojis.forEach((e) => {\n  emoteMap[e.name] = e.imageUrl;\n});\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/RoomChat.tsx",
    "content": "import { Room, RoomUser } from \"@dogehouse/kebab\";\nimport React, { useMemo } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../../global-stores/useCurrentRoomIdStore\";\nimport { RoomChatInput } from \"./RoomChatInput\";\nimport { RoomChatList } from \"./RoomChatList\";\nimport { RoomChatMentions } from \"./RoomChatMentions\";\ninterface ChatProps {\n  room: Room;\n  users: RoomUser[];\n}\n\nexport const RoomChat: React.FC<ChatProps> = ({ users, room }) => {\n  const userMap = useMemo(() => {\n    const map: Record<string, RoomUser> = {};\n    users.forEach((u) => {\n      map[u.id] = u;\n    });\n    return map;\n  }, [users]);\n\n  const { currentRoomId } = useCurrentRoomIdStore();\n\n  return (\n    <div\n      className={`flex flex-1 w-full md:mb-7 overflow-y-auto bg-primary-900 md:bg-primary-800 h-full rounded-8`}\n    >\n      <div className={`flex flex-1 w-full flex-col md:mt-4`}>\n        <RoomChatList room={room} userMap={userMap} />\n        <RoomChatMentions users={users} />\n        <RoomChatInput users={users} />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/RoomChatInput.tsx",
    "content": "import { Room, RoomUser, UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport React, { useRef, useState, useEffect } from \"react\";\nimport { Smiley } from \"../../../icons\";\nimport { createChatMessage } from \"../../../lib/createChatMessage\";\nimport { showErrorToast } from \"../../../lib/showErrorToast\";\nimport { useConn, useWrappedConn } from \"../../../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../../../shared-hooks/useTypeSafeTranslation\";\nimport { Input } from \"../../../ui/Input\";\nimport { customEmojis, CustomEmote } from \"./EmoteData\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\nimport { EmojiPicker } from \"../../../ui/EmojiPicker\";\nimport { useEmojiPickerStore } from \"../../../global-stores/useEmojiPickerStore\";\nimport { navigateThroughQueriedUsers } from \"./navigateThroughQueriedUsers\";\nimport { navigateThroughQueriedEmojis } from \"./navigateThroughQueriedEmojis\";\nimport { useTypeSafeQuery } from \"../../../shared-hooks/useTypeSafeQuery\";\nimport { useCurrentRoomIdStore } from \"../../../global-stores/useCurrentRoomIdStore\";\nimport { useScreenType } from \"../../../shared-hooks/useScreenType\";\nimport { useCurrentRoomFromCache } from \"../../../shared-hooks/useCurrentRoomFromCache\";\nimport Dolma from '@dogehouse/dolma';\n\ninterface ChatInputProps {\n  users: RoomUser[];\n}\n\nexport const RoomChatInput: React.FC<ChatInputProps> = ({ users }) => {\n  const { message, setMessage } = useRoomChatStore();\n  const { setQueriedUsernames } = useRoomChatMentionStore();\n  const { setOpen, open, queryMatches } = useEmojiPickerStore();\n  const conn = useConn();\n  const dolma = new Dolma(customEmojis);\n  const wConn = useWrappedConn();\n  const me = conn.user;\n  const inputRef = useRef<HTMLInputElement>(null);\n  const [lastMessageTimestamp, setLastMessageTimestamp] = useState<number>(0);\n  const { t } = useTypeSafeTranslation();\n  const screenType = useScreenType();\n\n  let position = 0;\n\n  useEffect(() => {\n    if (!open && screenType !== \"fullscreen\") inputRef.current?.focus(); // Prevent autofocus on mobile\n  }, [open, screenType]);\n\n  const data = useCurrentRoomFromCache();\n\n  if (data && !(\"error\" in data) && data.room.chatMode === \"disabled\") {\n    return (\n      <p className=\"my-4 text-center text-primary-300\">\n        {t(\"modules.roomChat.disabled\")}\n      </p>\n    );\n  }\n\n  const handleSubmit = async (\n    e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement>\n  ) => {\n    e.preventDefault();\n\n    if (!me) return;\n\n    if (me.id in useRoomChatStore.getState().bannedUserIdMap) {\n      showErrorToast(t(\"modules.roomChat.bannedAlert\"));\n      return;\n    }\n\n    if (\n      data &&\n      !(\"error\" in data) &&\n      Date.now() - lastMessageTimestamp <= data.room.chatThrottle\n    ) {\n      showErrorToast(t(\"modules.roomChat.waitAlert\"));\n      return;\n    }\n\n    const tmp = message;\n    // const messageData = createChatMessage(tmp, users);\n    const messageData = dolma.encode(message);\n    console.log(messageData);\n\n    messageData.whisperedTo = await Promise.all(messageData.whisperedTo.map(async (uname = \"\") => {\n      const u = await wConn.query.getUserProfile(uname);\n      if(\"id\" in u!) return u.id;\n      return \"\";\n    }));\n\n\n    // dont empty the input, if no tokens\n    if (!messageData.tokens.length) return;\n    setMessage(\"\");\n\n    if (\n      !message ||\n      !message.trim() ||\n      !message.replace(/[\\u200B-\\u200D\\uFEFF]/g, \"\")\n    ) {\n      return;\n    }\n\n    conn.send(\"send_room_chat_msg\", messageData);\n    setQueriedUsernames([]);\n\n    setLastMessageTimestamp(Date.now());\n  };\n\n  // useEffect(() => {\n  //   const id = setInterval(() => {\n  //     conn.send(\"send_room_chat_msg\", createChatMessage(\"spam\"));\n  //   }, 1001);\n\n  //   return () => {\n  //     clearInterval(id);\n  //   };\n  //   // eslint-disable-next-line react-hooks/exhaustive-deps\n  // }, []);\n\n  return (\n    <form onSubmit={handleSubmit} className={`pb-3 px-4 pt-2 flex flex-col`}>\n      <div className={`mb-1 block relative`}>\n        <EmojiPicker\n          emojiSet={customEmojis as any}\n          onEmojiSelect={(emoji) => {\n            position =\n              (position === 0\n                ? inputRef!.current!.selectionStart\n                : position + 2) || 0;\n\n            let msg = '';\n\n            if ((message.match(/:/g)?.length ?? 0) % 2) {\n              msg = message.split('').reverse().join('');\n              msg = msg.replace(msg.split(':')[0] + ':', '');\n              msg = msg.split('').reverse().join('');\n            } else {\n              msg = message;\n            }\n\n            const newMsg = [\n              msg.slice(0, position),\n              (`:${emoji.short_names[0]}:` || \"\") +\n                \" \",\n              msg.slice(position),\n            ].join(\"\");\n            setMessage(newMsg);\n          }}\n        />\n      </div>\n      <div className=\"flex items-stretch\">\n        <div className=\"flex-1\">\n          {data && \"room\" in data && data.room.chatMode === \"follower_only\" ? (\n            <div className=\"text-primary-300 mb-1\">Follower mode</div>\n          ) : null}\n          <div className=\"flex flex-1 lg:mr-0 items-center bg-primary-700 rounded-8\">\n            <Input\n              maxLength={512}\n              placeholder={t(\"modules.roomChat.sendMessage\")}\n              value={message}\n              onChange={(e) => setMessage(e.target.value)}\n              id=\"room-chat-input\"\n              transparent\n              ref={inputRef}\n              autoComplete=\"off\"\n              onKeyDown={\n                queryMatches.length\n                  ? navigateThroughQueriedEmojis\n                  : navigateThroughQueriedUsers\n              }\n              onFocus={() => {\n                setOpen(false);\n                position = 0;\n              }}\n            />\n            <div\n              className={`right-12 cursor-pointer flex flex-row-reverse fill-current text-primary-200 mr-3`}\n              onClick={() => {\n                setOpen(!open);\n                position = 0;\n              }}\n            >\n              <Smiley style={{ inlineSize: \"23px\" }}></Smiley>\n            </div>\n          </div>\n        </div>\n\n        {/* Send button (mobile only) */}\n        {/* {chatIsSidebar ? null : (\n          <Button\n            onClick={handleSubmit}\n            variant=\"small\"\n            style={{ padding: \"10px 12px\" }}\n          >\n            <Codicon name=\"arrowRight\" />\n          </Button>\n        )} */}\n      </div>\n    </form>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/RoomChatList.tsx",
    "content": "import { Message, Room, RoomUser } from \"@dogehouse/kebab\";\nimport normalizeUrl from \"normalize-url\";\nimport React, { useContext, useEffect, useRef } from \"react\";\nimport { useVirtual, VirtualItem } from \"react-virtual\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { useCurrentRoomInfo } from \"../../../shared-hooks/useCurrentRoomInfo\";\nimport { useTypeSafeTranslation } from \"../../../shared-hooks/useTypeSafeTranslation\";\nimport { ParseTextToTwemoji, StaticTwemoji } from \"../../../ui/Twemoji\";\nimport { UserPreviewModalContext } from \"../UserPreviewModalProvider\";\nimport { Emote } from \"./Emote\";\nimport { EmoteKeys } from \"./EmoteData\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\nimport { useResize } from \"../useResize\";\n\ninterface ChatListProps {\n  room: Room;\n  userMap: Record<string, RoomUser>;\n}\n\ninterface BadgeIconData {\n  emoji: string;\n  title: string;\n}\n\nexport const RoomChatList: React.FC<ChatListProps> = ({ room, userMap }) => {\n  const { setData } = useContext(UserPreviewModalContext);\n  const { messages, toggleFrozen } = useRoomChatStore();\n  const me = useConn().user;\n  const { isMod: iAmMod, isCreator: iAmCreator } = useCurrentRoomInfo();\n  const bottomRef = useRef<null | HTMLDivElement>(null);\n  const chatListRef = useRef<null | HTMLDivElement>(null);\n  const {\n    isRoomChatScrolledToTop,\n    setIsRoomChatScrolledToTop,\n    message,\n    setMessage,\n  } = useRoomChatStore();\n  const { t } = useTypeSafeTranslation();\n\n  // Only scroll into view if not manually scrolled to top\n  useEffect(() => {\n    if (!isRoomChatScrolledToTop) {\n      chatListRef.current?.scrollTo(0, chatListRef.current.scrollHeight);\n    }\n  });\n  const windowSize = useResize();\n  const rowVirtualizer = useVirtual({\n    overscan: 10,\n    size: messages.length,\n    parentRef: chatListRef,\n    estimateSize: React.useCallback(() => windowSize.y * 0.2, [windowSize]),\n  });\n\n  const getBadgeIcon = (m: Message) => {\n    const user = userMap[m.userId];\n    const isCreator = room.creatorId === user?.id;\n    let badge: React.ReactNode | null = null;\n    if (isCreator) {\n      badge = (\n        <Emote title=\"Admin\" alt=\"admin\" size=\"small\" emote=\"coolhouse\" />\n      );\n    } else if (user?.roomPermissions?.isMod) {\n      badge = <Emote title=\"Mod\" alt=\"mod\" size=\"small\" emote=\"dogehouse\" />;\n    } else if (user?.roomPermissions?.isSpeaker) {\n      badge = <StaticTwemoji emoji=\"📣\" title=\"Speaker\" />;\n    }\n    return <span style={{ marginRight: 4 }}>{badge}</span>;\n  };\n\n  return (\n    <div\n      className={`flex px-5 flex-1 overflow-y-auto chat-message-container scrollbar-thin scrollbar-thumb-primary-700`}\n      ref={chatListRef}\n      onScroll={() => {\n        if (!chatListRef.current) return;\n        const { scrollTop, offsetHeight, scrollHeight } = chatListRef.current;\n        const isOnBottom =\n          Math.abs(scrollTop + offsetHeight - scrollHeight) <= 1;\n\n        setIsRoomChatScrolledToTop(!isOnBottom);\n        if (isOnBottom) {\n          useRoomChatMentionStore.getState().resetIAmMentioned();\n        }\n      }}\n      onMouseEnter={toggleFrozen}\n      onMouseLeave={toggleFrozen}\n    >\n      <div\n        className=\"w-full h-full mt-auto\"\n        style={{\n          height: `${rowVirtualizer.totalSize}px`,\n          width: \"100%\",\n          position: \"relative\",\n        }}\n      >\n        {rowVirtualizer.virtualItems.map(\n          ({ index: idx, start, measureRef, size }: VirtualItem) => {\n            const index = messages.length - idx - 1;\n            const badgeIcon = getBadgeIcon(messages[index]);\n            return (\n              <div\n                ref={measureRef}\n                style={{\n                  position: \"absolute\",\n                  top: 0,\n                  left: 0,\n                  width: \"100%\",\n                  transform: `translateY(${\n                    messages[index].isWhisper ? start : start\n                  }px)`,\n                }}\n                key={messages[index].id}\n                className=\"py-1\"\n              >\n                <div\n                  className={`flex flex-col flex-shrink-0 w-full ${\n                    messages[index].isWhisper\n                      ? \"bg-primary-700 rounded p-1 pt-0\"\n                      : \"\"\n                  }`}\n                >\n                  {/* Whisper label */}\n                  {messages[index].isWhisper ? (\n                    <div className=\"flex mb-1 text-sm text-primary-300 px-1 w-16 mt-1 text-center\">\n                      {t(\"modules.roomChat.whisper\")}\n                    </div>\n                  ) : null}\n                  <div className={`flex items-center px-1`}>\n                    <div\n                      className={`block break-words overflow-hidden max-w-full items-start flex-1 text-primary-100`}\n                      key={messages[index].id}\n                    >\n                      {badgeIcon}\n                      <button\n                        onClick={(e) => {\n                          // Auto mention on shift click\n                          if (e.shiftKey && messages[index].userId !== me.id) {\n                            setMessage(\n                              message +\n                                \"@\" +\n                                messages[index].username\n                            );\n                            document.getElementById(\"room-chat-input\")?.focus();\n\n                            return;\n                          }\n\n                          setData({\n                            userId: messages[index].userId,\n                            message:\n                              (me?.id === messages[index].userId ||\n                                iAmCreator ||\n                                (iAmMod &&\n                                  room.creatorId !== messages[index].userId)) &&\n                              !messages[index].deleted\n                                ? messages[index]\n                                : undefined,\n                          });\n                        }}\n                        // DO NOT CHANGE FONT ON THIS BUTTON, IT CRASHES FIREFOX\n                        className={`inline hover:underline font-bold focus:outline-none`}\n                        style={{\n                          textDecorationColor: messages[index].color,\n                          color: messages[index].color,\n                        }}\n                      >\n                        {messages[index].username}\n                      </button>\n                      <span className={`inline`}>: </span>\n                      <div className={`inline`}>\n                        {messages[index].deleted ? (\n                          <span className=\"inline text-primary-300 italic\">\n                            {t(\"modules.roomChat.messageDeletion.message\") + \"\"}\n                            {messages[index].deleterId ===\n                            messages[index].userId\n                              ? t(\"modules.roomChat.messageDeletion.retracted\")\n                              : t(\"modules.roomChat.messageDeletion.deleted\")}\n                          </span>\n                        ) : (\n                          messages[index].tokens.map(({ t: token, v }, i) => {\n                            switch (token) {\n                              case \"text\":\n                                return (\n                                  <React.Fragment\n                                    key={i}\n                                  >{`${v} `}</React.Fragment>\n                                );\n                              case \"emote\":\n                                return <Emote emote={v as EmoteKeys} key={i} />;\n\n                              case \"mention\":\n                                return (\n                                  <React.Fragment key={i}>\n                                    <button\n                                      onClick={() => {\n                                        setData({ userId: v });\n                                      }}\n                                      className={`inline flex-1 focus:outline-none ${\n                                        v === me?.username\n                                          ? \"bg-accent text-button px-1 rounded text-md\"\n                                          : \"\"\n                                      }`}\n                                      style={{\n                                        textDecorationColor:\n                                          v === me?.username\n                                            ? \"\"\n                                            : messages[index].color,\n                                        color:\n                                          v === me?.username\n                                            ? \"\"\n                                            : messages[index].color,\n                                      }}\n                                    >\n                                      @{v}\n                                    </button>\n                                    {\"\"}\n                                  </React.Fragment>\n                                );\n                              case \"link\":\n                                try {\n                                  return (\n                                    <a\n                                      target=\"_blank\"\n                                      rel=\"noreferrer noopener\"\n                                      href={v}\n                                      className={`inline flex-1 hover:underline text-accent`}\n                                      key={i}\n                                    >\n                                      {normalizeUrl(v, { stripProtocol: true })}\n                                      {\"\"}\n                                    </a>\n                                  );\n                                } catch {\n                                  return null;\n                                }\n                              case \"block\":\n                                return (\n                                  <React.Fragment key={i}>\n                                    <span\n                                      className={\n                                        \"inline bg-primary-600 px-1 rounded whitespace-pre-wrap font-mono\"\n                                      }\n                                    >\n                                      {v}\n                                    </span>\n                                    {\"\"}\n                                  </React.Fragment>\n                                );\n                              case \"emoji\":\n                                return <>\n                                <ParseTextToTwemoji text={v}></ParseTextToTwemoji>\n                                </>;\n                              default:\n                                return null;\n                            }\n                          })\n                        )}\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            );\n          }\n        )}\n        {/* {messages.length === 0 ? (\n        <div className=\"flex\">{t(\"modules.roomChat.welcomeMessage\")}</div>\n      ) : null} */}\n      </div>\n      <div ref={bottomRef} />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/RoomChatMentions.tsx",
    "content": "import { BaseUser, RoomUser } from \"@dogehouse/kebab\";\nimport React, { useEffect } from \"react\";\nimport { mentionRegex } from \"../../../lib/constants\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { SingleUser } from \"../../../ui/UserAvatar\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\n\ninterface RoomChatMentionsProps {\n  users: RoomUser[];\n}\n\nexport const RoomChatMentions: React.FC<RoomChatMentionsProps> = ({\n  users,\n}) => {\n  const me = useConn().user;\n\n  const { message, setMessage } = useRoomChatStore();\n\n  const {\n    activeUsername,\n    setActiveUsername,\n    queriedUsernames,\n    setQueriedUsernames,\n  } = useRoomChatMentionStore();\n\n  function addMention(m: BaseUser) {\n    setMessage(\n      message.substring(0, message.lastIndexOf(\"@\") + 1) + m.username + \" \"\n    );\n    setQueriedUsernames([]);\n\n    // Re-focus input after mention was clicked\n    document.getElementById(\"room-chat-input\")?.focus();\n  }\n\n  useEffect(() => {\n    // regex to match mention patterns\n    const mentionMatches = message.match(mentionRegex);\n\n    // query usernames for matched patterns\n    if (mentionMatches && me) {\n      const mentionsList = mentionMatches[0].replace(/@|#/g, \"\").split(\" \");\n      const useMention = mentionsList[mentionsList.length - 1];\n\n      // hide usernames list if user continues typing without selecting\n      if (message[message.lastIndexOf(useMention) + useMention.length]) {\n        setQueriedUsernames([]);\n      } else {\n        const usernameMatches = users.filter(\n          ({ id, username, displayName }) =>\n            (username?.toLowerCase().includes(useMention?.toLowerCase()) ||\n              displayName?.toLowerCase().includes(useMention?.toLowerCase())) &&\n            me.id !== id\n        );\n\n        const firstFive = usernameMatches.slice(0, 5);\n        setQueriedUsernames(firstFive);\n        if (firstFive.length) setActiveUsername(firstFive[0].id);\n      }\n    } else {\n      // Hide mentions when message is sent\n      setQueriedUsernames([]);\n    }\n\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [message]);\n\n  if (queriedUsernames.length) {\n    return (\n      <div className={`flex flex-col pb-1 bg-primary-800`}>\n        {queriedUsernames.map((m) => (\n          <button\n            className={`flex py-3 items-center px-6 justify-start focus:outline-none truncate ${\n              activeUsername === m.id ? \"bg-primary-700\" : \"\"\n            }`}\n            key={m.id}\n            onClick={() => addMention(m)}\n          >\n            <SingleUser size=\"xs\" src={m.avatarUrl} />\n            <div className={`pl-3 m-0 text-primary-200 truncate`}>\n              {m.displayName}\n              {m.displayName !== m.username ? ` (${m.username})` : null}\n            </div>\n          </button>\n        ))}\n      </div>\n    );\n  }\n\n  return <></>;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/navigateThroughQueriedEmojis.ts",
    "content": "import { useEmojiPickerStore } from \"../../../global-stores/useEmojiPickerStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\n\nexport const navigateThroughQueriedEmojis = (e: any) => {\n  const { message, setMessage } = useRoomChatStore.getState();\n  const {\n    queryMatches,\n    keyboardHoveredEmoji,\n    setQueryMatches,\n    setKeyboardHoveredEmoji,\n    setOpen,\n    query,\n  } = useEmojiPickerStore.getState();\n\n  // Use dom method, GlobalHotkeys apparently don't catch arrow-key events on inputs\n  if (\n    ![\"ArrowLeft\", \"ArrowRight\", \"Enter\"].includes(e.code) ||\n    !queryMatches.length\n  ) {\n    return;\n  }\n\n  e.preventDefault();\n\n  let changeToIndex: number | null = null;\n  const activeIndex = queryMatches.findIndex(\n    (emoji) => emoji.name === keyboardHoveredEmoji\n  );\n\n  if (e.code === \"ArrowLeft\") {\n    changeToIndex =\n      activeIndex === 0 ? queryMatches.length - 1 : activeIndex - 1;\n  } else if (e.code === \"ArrowRight\") {\n    changeToIndex =\n      activeIndex === queryMatches.length - 1 ? 0 : activeIndex + 1;\n  } else if (e.code === \"Enter\") {\n    const selected = queryMatches[activeIndex];\n\n    setMessage(message.slice(0, ~(query.length - 1)) + `:${selected.name}: `);\n    setOpen(false);\n    setQueryMatches([]);\n  }\n\n  // navigate to next/prev mention suggestion item\n  if (changeToIndex !== null) {\n    setKeyboardHoveredEmoji(queryMatches[changeToIndex]?.name);\n  }\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/navigateThroughQueriedUsers.ts",
    "content": "import { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\n\nexport const navigateThroughQueriedUsers = (e: any) => {\n  const {\n    queriedUsernames,\n    setQueriedUsernames,\n    activeUsername,\n    setActiveUsername,\n  } = useRoomChatMentionStore.getState();\n  const { message, setMessage } = useRoomChatStore.getState();\n\n  // Use dom method, GlobalHotkeys apparently don't catch arrow-key events on inputs\n  if (\n    ![\"ArrowUp\", \"ArrowDown\", \"Enter\", \"Tab\"].includes(e.code) ||\n    !queriedUsernames.length\n  ) {\n    return;\n  }\n\n  e.preventDefault();\n\n  let changeToIndex: number | null = null;\n  const activeIndex = queriedUsernames.findIndex(\n    (username) => username.id === activeUsername\n  );\n\n  if (e.code === \"ArrowUp\") {\n    changeToIndex =\n      activeIndex === 0 ? queriedUsernames.length - 1 : activeIndex - 1;\n  } else if (e.code === \"ArrowDown\") {\n    changeToIndex =\n      activeIndex === queriedUsernames.length - 1 ? 0 : activeIndex + 1;\n  } else if (e.code === \"Enter\" || e.code === \"Tab\") {\n    const selected = queriedUsernames[activeIndex];\n\n    setMessage(\n      `${message.substring(0, message.lastIndexOf(\"@\") + 1)}${\n        selected.username\n      } `\n    );\n    setQueriedUsernames([]);\n  }\n\n  // navigate to next/prev mention suggestion item\n  if (changeToIndex !== null) {\n    setActiveUsername(queriedUsernames[changeToIndex]?.id);\n  }\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/useRoomChatMentionStore.ts",
    "content": "import { BaseUser } from \"@dogehouse/kebab\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../../sound-effects/useSoundEffectStore\";\n\nexport const useRoomChatMentionStore = create(\n  combine(\n    {\n      queriedUsernames: [] as BaseUser[],\n      activeUsername: \"\",\n      iAmMentioned: 0,\n    },\n    (set) => ({\n      setQueriedUsernames: (queriedUsernames: BaseUser[]) =>\n        set({\n          queriedUsernames,\n        }),\n      setActiveUsername: (activeUsername: string) => {\n        return set({\n          activeUsername,\n        });\n      },\n      resetIAmMentioned: () =>\n        set({\n          iAmMentioned: 0,\n        }),\n      incrementIAmMentioned: () => {\n        useSoundEffectStore.getState().playSoundEffect(\"roomChatMention\");\n        set((x) => ({ iAmMentioned: x.iAmMentioned + 1 }));\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/room/chat/useRoomChatStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\n\ninterface TextToken {\n  t: \"text\";\n  v: string;\n}\ninterface MentionToken {\n  t: \"mention\";\n  v: string;\n}\ninterface LinkToken {\n  t: \"link\";\n  v: string;\n}\n\ninterface BlockToken {\n  t: \"block\";\n  v: string;\n}\n\ninterface EmoteToken {\n  t: \"emote\";\n  v: string;\n}\n\ninterface EmojiToken {\n  t: \"emoji\";\n  v: string;\n}\n\nexport type RoomChatMessageToken =\n  | TextToken\n  | MentionToken\n  | LinkToken\n  | BlockToken\n  | EmoteToken\n  | EmojiToken;\n\nconst colors = [\n  \"#ff2366\",\n  \"#fd51d9\",\n  \"#face15\",\n  \"#8d4de8\",\n  \"#6859ea\",\n  \"#7ed321\",\n  \"#56b2ba\",\n  \"#00CCFF\",\n  \"#FF9900\",\n  \"#FFFF66\",\n];\n\nfunction generateColorFromString(str: string) {\n  let sum = 0;\n  for (let x = 0; x < str.length; x++) sum += x * str.charCodeAt(x);\n  return colors[sum % colors.length];\n}\n\nexport interface RoomChatMessage {\n  id: string;\n  userId: string;\n  avatarUrl: string;\n  color: string;\n  username: string;\n  displayName: string;\n  tokens: RoomChatMessageToken[];\n  deleted?: boolean;\n  deleterId?: string;\n  sentAt: string;\n  isWhisper?: boolean;\n}\n\nexport const useRoomChatStore = create(\n  combine(\n    {\n      open: false,\n      bannedUserIdMap: {} as Record<string, boolean>,\n      messages: [] as RoomChatMessage[],\n      newUnreadMessages: false,\n      message: \"\" as string,\n      isRoomChatScrolledToTop: false,\n      frozen: false,\n    },\n    (set) => ({\n      unbanUser: (userId: string) =>\n        set(({ bannedUserIdMap: { [userId]: _, ...banMap }, ...s }) => ({\n          messages: s.messages.filter((m) => m.userId !== userId),\n          bannedUserIdMap: banMap,\n        })),\n      addBannedUser: (userId: string) =>\n        set((s) => ({\n          messages: s.messages.filter((m) => m.userId !== userId),\n          bannedUserIdMap: { ...s.bannedUserIdMap, [userId]: true },\n        })),\n      addMessage: (m: RoomChatMessage) =>\n        set((s) => ({\n          newUnreadMessages: !s.open,\n          messages: [\n            { ...m, color: generateColorFromString(m.userId) },\n            ...(s.messages.length <= 100 || s.frozen\n              ? s.messages\n              : s.messages.slice(0, 100)),\n          ],\n        })),\n      setMessages: (messages: RoomChatMessage[]) =>\n        set((s) => ({\n          messages,\n        })),\n      clearChat: () =>\n        set({\n          messages: [],\n          newUnreadMessages: false,\n          bannedUserIdMap: {},\n        }),\n      reset: () =>\n        set({\n          messages: [],\n          newUnreadMessages: false,\n          message: \"\",\n          bannedUserIdMap: {},\n        }),\n      toggleOpen: () =>\n        set((s) => {\n          // Reset mention state\n          useRoomChatMentionStore.getState().resetIAmMentioned();\n          if (s.open) {\n            return {\n              open: false,\n              newUnreadMessages: false,\n            };\n          } else {\n            return {\n              open: true,\n              newUnreadMessages: false,\n            };\n          }\n        }),\n      setMessage: (message: string) =>\n        set({\n          message,\n        }),\n      setOpen: (open: boolean) => set((s) => ({ ...s, open })),\n      setIsRoomChatScrolledToTop: (isRoomChatScrolledToTop: boolean) =>\n        set({\n          isRoomChatScrolledToTop,\n        }),\n      toggleFrozen: () => set((s) => ({ frozen: !s.frozen })),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/room/mobile/RoomOverlay.tsx",
    "content": "import React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useSpring, a, config } from \"react-spring\";\nimport { useDrag } from \"react-use-gesture\";\nimport {\n  SolidDeafened,\n  SolidDeafenedOff,\n  SolidFriendsAdd,\n  SolidMicrophone,\n  SolidMicrophoneOff,\n  SolidSettings,\n  SolidSimpleMegaphone,\n} from \"../../../icons\";\nimport { RoomChatController } from \"../RoomChatController\";\nimport useWindowSize from \"../../../shared-hooks/useWindowSize\";\nimport { BoxedIcon } from \"../../../ui/BoxedIcon\";\nimport useViewportSize from \"../../../shared-hooks/useViewportSize\";\n\ninterface RoomOverlayProps {\n  mute?: {\n    isMuted: boolean;\n    onMute: () => void;\n  };\n  deaf?: {\n    isDeaf: boolean;\n    onDeaf: () => void;\n  };\n  onInvitePeopleToRoom?: () => void;\n  onRoomSettings?: () => void;\n  askToSpeak?: () => void;\n  setListener: () => void;\n  canSpeak: boolean;\n}\n\nconst RoomOverlay: React.FC<RoomOverlayProps> = ({\n  mute,\n  deaf,\n  onInvitePeopleToRoom,\n  onRoomSettings,\n  askToSpeak,\n  setListener,\n  canSpeak,\n}) => {\n  const { height: windowHeight } = useWindowSize();\n  const { height: viewportHeight } = useViewportSize();\n  const height = windowHeight - 30 - 100;\n  const [{ y }, set] = useSpring(() => ({ y: height }));\n\n  const open = () => {\n    set({\n      y: 0,\n      immediate: false,\n      config: { mass: 1, tension: 200, friction: 25 },\n    });\n  };\n\n  const close = (velocity = 0) => {\n    set({\n      y: height,\n      immediate: false,\n      config: { ...config.default, velocity: velocity * 1.2 }, // @todo clamp velo between 0 and something\n    });\n  };\n\n  const bind = useDrag(\n    ({ last, vxvy: [, vy], movement: [, my], cancel, canceled }) => {\n      if (my > height) cancel(); // @todo fix weird bottom spacing\n      if (last) {\n        if (my > height * 0.8 || vy > 0.1) close(vy);\n        else open();\n      } else {\n        set({ y: my, immediate: true });\n      }\n    },\n    {\n      initial: () => [0, y.get()],\n      filterTaps: true,\n      bounds: { top: 0 },\n      rubberband: true,\n    }\n  );\n\n  const bgStyle = {\n    opacity: y.to([0, height], [0.8, 0], \"clamp\"),\n    display: y.to((py) => (py < height ? \"block\" : \"none\")),\n  };\n\n  return createPortal(\n    <>\n      <a.div\n        className=\"w-screen h-screen absolute left-0 bg-black z-10 opacity-100\"\n        style={bgStyle}\n        onClick={() => close()}\n      ></a.div>\n      <a.div\n        className=\"bg-primary-800 w-full rounded-t-20 z-10 absolute bottom-0 flex flex-col\"\n        style={{\n          bottom: `calc(-100% + ${height + 100 + 30}px)`,\n          height: viewportHeight - 30,\n          y,\n          zIndex: 11,\n          touchAction: \"none\",\n        }}\n      >\n        <div\n          className=\"bg-primary-600 rounded-full w-6 absolute top-3 left-2/4 transform -translate-x-1/2\"\n          style={{ height: 4 }}\n        ></div>\n        {/* We bind it to the header so scroll in chat doesn't close the overlay */}\n        <a.div {...bind()} className=\"flex justify-between p-4.5\">\n          <div>\n            {canSpeak ? (\n              <BoxedIcon\n                className={`w-9 h-6.5 ${\n                  !mute?.isMuted && !deaf?.isDeaf ? \"bg-accent text-button\" : \"\"\n                }`}\n                onClick={mute?.onMute}\n                hover\n              >\n                {mute?.isMuted || deaf?.isDeaf ? (\n                  <SolidMicrophoneOff width=\"20\" height=\"20\" />\n                ) : (\n                  <SolidMicrophone width=\"20\" height=\"20\" />\n                )}\n              </BoxedIcon>\n            ) : (\n              <BoxedIcon\n                className={`w-9 h-6.5 ${\n                  !askToSpeak ? \"bg-accent text-button\" : \"\"\n                }`}\n                hover\n                onClick={askToSpeak ? askToSpeak : setListener}\n              >\n                <SolidSimpleMegaphone width=\"20\" height=\"20\" />\n              </BoxedIcon>\n            )}\n          </div>\n          <div className=\"flex\">\n            {onRoomSettings ? (\n              <BoxedIcon\n                circle\n                className=\"h-6.5 w-6.5 mr-3\"\n                onClick={onRoomSettings}\n                hover\n              >\n                <SolidSettings width=\"20\" height=\"20\" />\n              </BoxedIcon>\n            ) : null}\n            <BoxedIcon\n              circle\n              className={`h-6.5 w-6.5 mr-3 ${\n                deaf?.isDeaf ? \"bg-accent text-button\" : \"\"\n              }`}\n              onClick={deaf?.onDeaf}\n              hover\n            >\n              {deaf?.isDeaf ? (\n                <SolidDeafenedOff width=\"20\" height=\"20\" />\n              ) : (\n                <SolidDeafened width=\"20\" height=\"20\" />\n              )}\n            </BoxedIcon>\n            <BoxedIcon\n              circle\n              className=\"h-6.5 w-6.5\"\n              hover\n              onClick={onInvitePeopleToRoom}\n            >\n              <SolidFriendsAdd width=\"20\" height=\"20\" />\n            </BoxedIcon>\n          </div>\n        </a.div>\n        <RoomChatController />\n      </a.div>\n    </>,\n    document.querySelector(\"#__next\")!\n  );\n};\n\nexport default RoomOverlay;\n"
  },
  {
    "path": "kibbeh/src/modules/room/useGetRoomByQueryParam.ts",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport { useRouter } from \"next/router\";\nimport { useEffect } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { isServer } from \"../../lib/isServer\";\nimport { validate as uuidValidate } from \"uuid\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport isElectron from \"is-electron\";\nimport { useRoomChatStore } from \"./chat/useRoomChatStore\";\n\nlet ipcRenderer: any = null;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\nexport const useGetRoomByQueryParam = () => {\n  const { setCurrentRoomId } = useCurrentRoomIdStore();\n  const { query } = useRouter();\n  const roomId = typeof query.id === \"string\" ? query.id : \"\";\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"joinRoomAndGetInfo\", roomId || \"\"],\n    {\n      enabled: uuidValidate(roomId) && !isServer,\n      refetchOnMount: \"always\",\n      onSuccess: ((d: JoinRoomAndGetInfoResponse | { error: string }) => {\n        if (d && !(\"error\" in d) && d.room) {\n          if (isElectron()) {\n            ipcRenderer.send(\"@room/joined\", true);\n          }\n          setCurrentRoomId(() => d.room.id);\n        }\n      }) as any,\n    },\n    [roomId]\n  );\n  const { push } = useRouter();\n\n  useEffect(() => {\n    if (roomId) {\n      setCurrentRoomId(roomId);\n      if (isElectron()) {\n        ipcRenderer.send(\"@room/joined\", true);\n      }\n    }\n  }, [roomId, setCurrentRoomId]);\n\n  const errMsg = data && \"error\" in data ? data.error : \"\";\n  const noData = !data;\n\n  useEffect(() => {\n    if (isLoading) {\n      return;\n    }\n    if (noData) {\n      setCurrentRoomId(null);\n      if (isElectron()) {\n        ipcRenderer.send(\"@room/joined\", false);\n      }\n      push(\"/dash\");\n      return;\n    }\n    if (errMsg) {\n      setCurrentRoomId(null);\n      if (isElectron()) {\n        ipcRenderer.send(\"@room/joined\", false);\n      }\n      console.log(errMsg, isLoading);\n      showErrorToast(errMsg);\n      push(\"/dash\");\n    }\n  }, [noData, errMsg, isLoading, push, setCurrentRoomId]);\n\n  return { data, isLoading };\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/useResize.ts",
    "content": "import React, { useEffect, useCallback, useState } from \"react\";\n\nfunction getWindowsSize() {\n  return { x: window.innerWidth, y: window.innerHeight };\n}\n\nexport const useResize = () => {\n  const [size, setSize] = useState(getWindowsSize());\n\n  const handleResize = useCallback(() => {\n    setSize(getWindowsSize());\n  }, []);\n\n  // register resize event listener and removes it\n  // calls handleResize\n  useEffect(() => {\n    window.addEventListener(\"resize\", handleResize, { passive: true });\n    return () => {\n      window.removeEventListener(\"resize\", handleResize);\n    };\n  }, [handleResize]);\n\n  return size;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/room/useSplitUsersIntoSections.tsx",
    "content": "import { JoinRoomAndGetInfoResponse, wrap } from \"@dogehouse/kebab\";\nimport React, { useContext } from \"react\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { SolidSimpleMegaphone } from \"../../icons\";\nimport { modalConfirm } from \"../../shared-components/ConfirmModal\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { BoxedIcon } from \"../../ui/BoxedIcon\";\nimport { RoomAvatar } from \"../../ui/RoomAvatar\";\nimport { UserPreviewModalContext } from \"./UserPreviewModalProvider\";\nimport { Emote } from \"./chat/Emote\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\n\nexport const useSplitUsersIntoSections = ({\n  room,\n  users,\n  activeSpeakerMap,\n  muteMap,\n  deafMap,\n}: JoinRoomAndGetInfoResponse) => {\n  const conn = useConn();\n  const { muted } = useMuteStore();\n  const { deafened } = useDeafStore();\n  const { setData } = useContext(UserPreviewModalContext);\n  const screenType = useScreenType();\n  const speakers: React.ReactNode[] = [];\n  const askingToSpeak: React.ReactNode[] = [];\n  const listeners: React.ReactNode[] = [];\n  let canIAskToSpeak = false;\n\n  users.forEach((u) => {\n    let arr = listeners;\n    if (u.id === room.creatorId || u.roomPermissions?.isSpeaker) {\n      arr = speakers;\n    } else if (u.roomPermissions?.askedToSpeak) {\n      arr = askingToSpeak;\n    } else if (u.id === conn.user.id) {\n      canIAskToSpeak = true;\n    }\n\n    let flair: React.ReactNode | undefined = undefined;\n\n    const isCreator = u.id === room.creatorId;\n    const isSpeaker = !!u.roomPermissions?.isSpeaker;\n    const canSpeak = isCreator || isSpeaker;\n    const isMuted = conn.user.id === u.id ? muted : muteMap[u.id];\n    const isDeafened = conn.user.id === u.id ? deafened : deafMap[u.id];\n\n    if (isCreator || u.roomPermissions?.isMod) {\n      flair = (\n        <Emote\n          emote={isCreator ? \"coolhouse\" : \"dogehouse\"}\n          alt={isCreator ? `admin` : `mod`}\n          title={isCreator ? `Admin` : `Mod`}\n          style={{ marginLeft: 4 }}\n          className={`w-3 h-3 ml-1`}\n        />\n      );\n    }\n\n    // for (let i = 0; i < 50; i++) {\n    arr.push(\n      <RoomAvatar\n        // key={u.id + i}\n        id={u.id}\n        canSpeak={canSpeak}\n        isMe={u.id === conn.user.id}\n        key={u.id}\n        src={u.avatarUrl}\n        username={u.username}\n        isBot={!!u.botOwnerId}\n        activeSpeaker={\n          canSpeak && !isMuted && !isDeafened && u.id in activeSpeakerMap\n        }\n        muted={canSpeak && isMuted && !isDeafened}\n        deafened={isDeafened}\n        onClick={() => {\n          setData({ userId: u.id });\n        }}\n        flair={flair}\n      />\n    );\n    // }\n  });\n\n  if (canIAskToSpeak && screenType !== \"fullscreen\") {\n    speakers.push(\n      <div key=\"megaphone\" className={`flex justify-center`}>\n        <BoxedIcon\n          onClick={() => {\n            modalConfirm(\"Would you like to ask to speak?\", () => {\n              wrap(conn).mutation.askToSpeak();\n            });\n          }}\n          style={{ width: 60, height: 60 }}\n          circle\n          className=\"flex-shrink-0\"\n          title=\"Request to speak\"\n        >\n          <SolidSimpleMegaphone width={20} height={20} />\n        </BoxedIcon>\n      </div>\n    );\n  }\n\n  return { speakers, listeners, askingToSpeak, canIAskToSpeak };\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/AddToCalendar.tsx",
    "content": "import React, { ReactNode, useEffect, useMemo, useRef, useState } from \"react\";\nimport { BaseOverlay } from \"../../ui/BaseOverlay\";\nimport { DropdownController } from \"../../ui/DropdownController\";\nimport { SettingsIcon } from \"../../ui/SettingsIcon\";\nimport makeUrls, { CalendarEvent } from \"./makeUrls\";\n\ntype CalendarURLs = ReturnType<typeof makeUrls>;\n\nconst useAutoFocus = () => {\n  const elementRef = useRef<HTMLElement>(null);\n\n  useEffect(() => {\n    const previous = document.activeElement;\n    const element = elementRef.current;\n\n    if (element) {\n      element.focus();\n    }\n\n    if (previous instanceof HTMLElement) {\n      return () => previous.focus();\n    }\n\n    return undefined;\n  }, []);\n\n  return elementRef;\n};\n\ntype OpenStateToggle = (event?: React.MouseEvent) => void;\n\nconst useOpenState = (initialOpen: boolean): [boolean, OpenStateToggle] => {\n  const [open, setOpen] = useState<boolean>(initialOpen);\n  const onToggle = () => setOpen((current) => !current);\n\n  useEffect(() => {\n    if (open) {\n      const onClose = () => setOpen(false);\n      document.addEventListener(\"click\", onClose);\n\n      return () => document.removeEventListener(\"click\", onClose);\n    }\n\n    return undefined;\n  }, [open, setOpen]);\n\n  return [open, onToggle];\n};\n\ntype CalendarRef = HTMLAnchorElement;\ntype CalendarProps = {\n  children: React.ReactNode;\n  filename?: string;\n  href: string;\n};\n\nconst Calendar = React.forwardRef<CalendarRef, CalendarProps>(\n  ({ children, filename = false, href }, ref) => (\n    <a\n      ref={ref}\n      download={filename}\n      href={href}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n    >\n      {children}\n    </a>\n  )\n);\n\nCalendar.displayName = \"Calendar\";\n\ntype DropdownProps = {\n  filename: string;\n  onToggle: OpenStateToggle;\n  urls: CalendarURLs;\n};\n\nconst Dropdown: React.FC<DropdownProps> = ({ filename, onToggle, urls }) => {\n  const ref = useAutoFocus() as React.RefObject<HTMLAnchorElement>;\n  const onKeyDown = (event: React.KeyboardEvent) => {\n    if (event.key === \"Escape\") {\n      onToggle();\n    }\n  };\n\n  return (\n    <BaseOverlay onKeyDown={onKeyDown} role=\"presentation\">\n      <SettingsIcon\n        onClick={onToggle}\n        a={{\n          href: urls.ics,\n          download: filename,\n          ref,\n        }}\n        label=\"Apple Calendar\"\n      />\n      <SettingsIcon\n        onClick={onToggle}\n        a={{ href: urls.google }}\n        label=\"Google\"\n      />\n      <SettingsIcon\n        onClick={onToggle}\n        a={{ href: urls.ics, download: filename }}\n        label=\"Outlook\"\n      />\n      <SettingsIcon\n        onClick={onToggle}\n        a={{ href: urls.outlook }}\n        label=\"Outlook Web App\"\n      />\n      <SettingsIcon onClick={onToggle} a={{ href: urls.yahoo }} label=\"Yahoo\" />\n    </BaseOverlay>\n  );\n};\n\ntype AddToCalendarProps = {\n  event: CalendarEvent;\n  open?: boolean;\n  filename?: string;\n  children: (tog: () => void) => ReactNode;\n};\n\nexport const AddToCalendar: React.FC<AddToCalendarProps> = ({\n  children,\n  event,\n  filename = \"download\",\n  open: initialOpen = false,\n}) => {\n  const [_, onToggle] = useOpenState(initialOpen);\n  const urls = useMemo<CalendarURLs>(() => makeUrls(event), [event]);\n\n  return (\n    <div className=\"relative\" title=\"Add to Calendar\">\n      <DropdownController\n        portal={false}\n        overlay={(close) => (\n          <Dropdown filename={filename} onToggle={close} urls={urls} />\n        )}\n      >\n        {children(onToggle)}\n      </DropdownController>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/CopyScheduleRoomLinkButton.tsx",
    "content": "import React, { useState } from \"react\";\nimport { Link, Link2 } from \"react-feather\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { BoxedIcon } from \"../../ui/BoxedIcon\";\nimport { copyTextToClipboard } from \"./copyToClipboard\";\n\ninterface CopyLinkButtonProps {\n  text: string;\n}\n\nexport const CopyScheduleRoomLinkButton: React.FC<CopyLinkButtonProps> = ({\n  text,\n}) => {\n  const [copied, setCopied] = useState(false);\n  return (\n    <BoxedIcon\n      onClick={() => {\n        if (copyTextToClipboard(text)) {\n          setCopied(true);\n        }\n      }}\n    >\n      <Link2 size={18} />\n    </BoxedIcon>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/CreateScheduledRoomModal.tsx",
    "content": "import DateFnsUtils from \"@date-io/date-fns\";\nimport { BaseUser } from \"@dogehouse/kebab\";\nimport { createMuiTheme, ThemeProvider } from \"@material-ui/core/styles\";\nimport { DateTimePicker, MuiPickersUtilsProvider } from \"@material-ui/pickers\";\nimport { add } from \"date-fns\";\nimport { Form, Formik } from \"formik\";\nimport React from \"react\";\nimport { InputField } from \"../../form-fields/InputField\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { ButtonLink } from \"../../ui/ButtonLink\";\nimport { InputErrorMsg } from \"../../ui/InputErrorMsg\";\nimport { Modal } from \"../../ui/Modal\";\n\nexport interface ScheduleRoomFormData {\n  name: string;\n  description: string;\n  cohosts: BaseUser[];\n  scheduledFor: Date;\n}\n\ninterface CreateRoomModalProps {\n  editInfo?: { intialValues: ScheduleRoomFormData; id: string };\n  onScheduledRoom: (data: ScheduleRoomFormData, resp: any) => void;\n  onRequestClose: () => void;\n}\n\nconst colors = {\n  p100: \"#dee3ea\",\n  p200: \"#b2bdcd\",\n  p300: \"#5d7290\",\n  p600: \"#323d4d\",\n  p700: \"#242c37\",\n  p800: \"#151a21\",\n  p900: \"#0b0e11\",\n  accent: \"#fd4d4d\",\n  accentHover: \"#fd6868\",\n  white: \"#FFF\",\n};\n\nconst theme = createMuiTheme({\n  palette: {\n    type: \"dark\",\n    primary: {\n      main: colors.accent,\n    },\n    secondary: {\n      main: colors.accent,\n    },\n  },\n  overrides: {\n    MuiPickersDay: {\n      day: {\n        \"&:hover\": {\n          backgroundColor: colors.p700,\n        },\n        color: colors.p100,\n      },\n      current: {\n        \"&:hover\": {\n          backgroundColor: colors.p700,\n        },\n        color: colors.p100,\n      },\n      daySelected: {\n        \"&:hover\": {\n          backgroundColor: colors.accentHover,\n        },\n        backgroundColor: colors.accent,\n        color: colors.white,\n      },\n      dayDisabled: {\n        backgroundColor: colors.p800,\n        color: colors.p600,\n      },\n    },\n    MuiPickerDTHeader: {\n      separator: {},\n      toolbar: {},\n    },\n    MuiPickerDTTabs: {\n      tabs: {\n        backgroundColor: colors.p900,\n      },\n    },\n    MuiPickersCalendar: {\n      week: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      progressContainer: {},\n      transitionContainer: {\n        backgroundColor: colors.p800,\n      },\n    },\n    MuiPickersCalendarHeader: {\n      switchHeader: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      transitionContainer: {\n        backgroundColor: colors.p800,\n      },\n      iconButton: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      daysHeader: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      dayLabel: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n    },\n    MuiPickersSlideTransition: {\n      transitionContainer: {},\n    },\n    MuiPickersYearSelectionStyles: {\n      container: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n    },\n    MuiPickersYear: {\n      root: {\n        backgroundColor: colors.p800,\n        color: colors.p300,\n      },\n      yearSelected: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      yearDisabled: {\n        backgroundColor: colors.p800,\n        color: colors.p600,\n      },\n    },\n    MuiPickersMonthSelection: {\n      container: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n    },\n    MuiPickersMonth: {\n      root: {\n        backgroundColor: colors.p800,\n        color: colors.p300,\n      },\n      monthSelected: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      monthDisabled: {\n        backgroundColor: colors.p800,\n        color: colors.p600,\n      },\n    },\n    MuiPickersTimePickerToolbar: {\n      separator: {},\n      toolbarLandscape: {},\n      hourMinuteLabel: {},\n      ampmLabel: {},\n    },\n    MuiPickersClock: {\n      container: {\n        backgroundColor: colors.p800,\n        color: colors.p100,\n      },\n      clock: {\n        backgroundColor: colors.p900,\n        color: colors.accent,\n      },\n      pin: {\n        backgroundColor: colors.accent,\n      },\n    },\n    MuiPickersClockNumber: {\n      clockNumber: {\n        color: colors.p200,\n      },\n      clockNumberSelected: {\n        color: colors.white,\n      },\n    },\n    MuiPickersClockPointer: {\n      animateTransform: {},\n      pointer: {\n        backgroundColor: colors.accent,\n      },\n      thumb: {\n        backgroundColor: colors.accent,\n        borderColor: colors.accent,\n      },\n      noPoint: {\n        backgroundColor: colors.accent,\n      },\n    },\n    MuiPickersModal: {\n      dialog: {\n        backgroundColor: colors.p800,\n      },\n      dialogRoot: {\n        backgroundColor: colors.p800,\n      },\n      dialogRootWider: {\n        backgroundColor: colors.p800,\n      },\n      withAdditionalAction: {\n        backgroundColor: colors.p800,\n      },\n    },\n    MuiPickersToolbar: {\n      toolbar: {\n        backgroundColor: colors.p900,\n        color: colors.p100,\n      },\n    },\n    MuiPickersToolbarButton: {\n      toolbarBtn: {\n        color: colors.p100,\n      },\n    },\n  },\n});\n\nexport const CreateScheduleRoomModal: React.FC<CreateRoomModalProps> = ({\n  onScheduledRoom,\n  onRequestClose,\n  editInfo,\n}) => {\n  const conn = useWrappedConn();\n  const { t } = useTypeSafeTranslation();\n  return (\n    <ThemeProvider theme={theme}>\n      <MuiPickersUtilsProvider utils={DateFnsUtils}>\n        <Modal isOpen onRequestClose={onRequestClose}>\n          <Formik<ScheduleRoomFormData>\n            initialValues={\n              editInfo?.intialValues || {\n                name: \"\",\n                description: \"\",\n                cohosts: [] as BaseUser[],\n                scheduledFor: add(new Date(), { days: 1 }),\n              }\n            }\n            validateOnChange={false}\n            validateOnBlur={false}\n            validate={({ name, scheduledFor }) => {\n              const errors: Record<string, string> = {};\n\n              if (name.length < 2) {\n                return {\n                  name: t(\"modules.scheduledRooms.modal.minLength\"),\n                };\n              }\n\n              if (scheduledFor.getTime() < new Date().getTime()) {\n                return {\n                  scheduledFor: t(\"modules.scheduledRooms.modal.needsFuture\"),\n                };\n              }\n\n              return errors;\n            }}\n            onSubmit={async (allData) => {\n              const { name, scheduledFor, ...data } = allData;\n              const scheduledForISO = scheduledFor.toISOString();\n              const resp = await (editInfo\n                ? conn.mutation.editScheduledRoom(editInfo.id, {\n                    name,\n                    scheduledFor: scheduledForISO,\n                    ...data,\n                  })\n                : conn.mutation.createScheduledRoom({\n                    name,\n                    scheduledFor: scheduledForISO,\n                    ...data,\n                  }));\n\n              if (\"error\" in resp && resp.error) {\n                showErrorToast(resp.error);\n                return;\n              } else {\n                onScheduledRoom(allData, resp);\n              }\n              onRequestClose();\n            }}\n          >\n            {({ setFieldValue, values, errors, isSubmitting }) => (\n              <Form className=\"flex-col w-full\">\n                <InputField\n                  name=\"name\"\n                  maxLength={60}\n                  placeholder={t(\"modules.scheduledRooms.modal.roomName\")}\n                  autoFocus\n                />\n                <div className={`flex mt-4 w-full flex-col`}>\n                  <DateTimePicker\n                    value={values.scheduledFor}\n                    minDate={new Date()}\n                    maxDate={add(new Date(), { months: 6 })}\n                    onChange={(x) => {\n                      if (x) {\n                        setFieldValue(\"scheduledFor\", x);\n                      }\n                    }}\n                  />\n                  {errors.scheduledFor ? (\n                    <div className={`flex mt-1`}>\n                      <InputErrorMsg>{errors.scheduledFor}</InputErrorMsg>\n                    </div>\n                  ) : null}\n                  <div className={`flex mt-4`}>\n                    <InputField\n                      textarea\n                      placeholder={t(\n                        \"modules.scheduledRooms.modal.roomDescription\"\n                      )}\n                      name=\"description\"\n                      maxLength={200}\n                    />\n                  </div>\n                </div>\n\n                <div\n                  className={`flex pt-4 space-x-3 col-span-full items-center`}\n                >\n                  <Button\n                    loading={isSubmitting}\n                    type=\"submit\"\n                    className={`mr-3`}\n                  >\n                    {t(\"common.ok\")}\n                  </Button>\n                  <ButtonLink type=\"button\" onClick={onRequestClose}>\n                    {t(\"common.cancel\")}\n                  </ButtonLink>\n                </div>\n              </Form>\n            )}\n          </Formik>\n        </Modal>\n      </MuiPickersUtilsProvider>\n    </ThemeProvider>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/EditScheduleRoomModalController.tsx",
    "content": "import { ScheduledRoom } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport {\n  CreateScheduleRoomModal,\n  ScheduleRoomFormData,\n} from \"./CreateScheduledRoomModal\";\n\ntype State = {\n  scheduleRoomToEdit: ScheduledRoom;\n  cursor: string;\n};\n\ninterface EditScheduleRoomModalControllerProps {\n  onScheduledRoom: (\n    editInfo: State,\n    data: ScheduleRoomFormData,\n    resp: any\n  ) => void;\n  children: (x: { onEdit: (y: State) => void }) => React.ReactNode;\n}\n\nexport const EditScheduleRoomModalController: React.FC<EditScheduleRoomModalControllerProps> = ({\n  onScheduledRoom,\n  children,\n}) => {\n  const [editInfo, setScheduleRoomToEdit] = useState<State | null>(null);\n\n  return (\n    <>\n      {editInfo ? (\n        <CreateScheduleRoomModal\n          editInfo={{\n            id: editInfo.scheduleRoomToEdit.id,\n            intialValues: {\n              cohosts: [],\n              description: editInfo.scheduleRoomToEdit.description,\n              name: editInfo.scheduleRoomToEdit.name,\n              scheduledFor: new Date(editInfo.scheduleRoomToEdit.scheduledFor),\n            },\n          }}\n          onScheduledRoom={(...vals) => onScheduledRoom(editInfo, ...vals)}\n          onRequestClose={() => setScheduleRoomToEdit(null)}\n        />\n      ) : null}\n      {children({ onEdit: setScheduleRoomToEdit })}\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/ScheduledRoomCard.tsx",
    "content": "import { ScheduledRoom } from \"@dogehouse/kebab\";\nimport { differenceInMilliseconds, isPast, isToday, sub } from \"date-fns\";\nimport { useRouter } from \"next/router\";\nimport React, { useContext, useEffect, useMemo, useState } from \"react\";\nimport { SolidCalendar, SolidRocket } from \"../../icons\";\nimport { modalConfirm } from \"../../shared-components/ConfirmModal\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { BoxedIcon } from \"../../ui/BoxedIcon\";\nimport { Button } from \"../../ui/Button\";\nimport { SingleUser } from \"../../ui/UserAvatar\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { CopyScheduleRoomLinkButton } from \"./CopyScheduleRoomLinkButton\";\nimport { Edit, Trash } from \"react-feather\";\nimport { AddToCalendar } from \"./AddToCalendar\";\n\ninterface ScheduledRoomCardProps {\n  onEdit: () => void;\n  onDeleteComplete: () => void;\n  info: ScheduledRoom;\n  noCopyLinkButton?: boolean;\n  noEditOrDeleteButton?: boolean;\n}\n\nexport const ScheduledRoomCard: React.FC<ScheduledRoomCardProps> = ({\n  onEdit,\n  onDeleteComplete,\n  noCopyLinkButton,\n  noEditOrDeleteButton = false,\n  info: { id, name, scheduledFor, creator, description, roomId },\n}) => {\n  const { push } = useRouter();\n  const {\n    mutateAsync: mutateAsyncStartRoom,\n    isLoading: isLoadingStartRoom,\n  } = useTypeSafeMutation(\"createRoomFromScheduledRoom\", {\n    onSuccess: ({ room }) => {\n      push(\"/room/\" + room.id);\n    },\n  });\n  const { mutateAsync, isLoading } = useTypeSafeMutation(\n    \"deleteScheduledRoom\",\n    {\n      onSuccess: () => {\n        onDeleteComplete();\n      },\n    }\n  );\n  const [, rerender] = useState(0);\n  const dt = useMemo(() => new Date(scheduledFor), [scheduledFor]);\n  const canStartRoom = useMemo(() => isPast(sub(dt, { minutes: 10 })), [dt]);\n  useEffect(() => {\n    let done = false;\n    const timeoutId = setTimeout(() => {\n      done = true;\n      rerender((x) => x + 1);\n    }, Math.min(differenceInMilliseconds(sub(dt, { minutes: 10 }), new Date()) + 1000, 0)); // + 1 second to be safe\n    return () => {\n      if (!done) {\n        clearTimeout(timeoutId);\n      }\n    };\n  }, [dt]);\n  const { conn } = useContext(WebSocketContext);\n  const me = conn?.user;\n  const { t } = useTypeSafeTranslation();\n  const isCreator = me?.id === creator.id;\n  const url = window.location.origin + `/scheduled-room/${id}`;\n  return (\n    <div className=\"flex\">\n      <div\n        className={`p-4 w-full text-base bg-primary-800 rounded-lg flex flex-col text-primary-100`}\n      >\n        <div className={`flex justify-between`}>\n          <div className=\"flex w-full\">\n            <div\n              className=\"flex flex-1 font-bold text-ellipsis overflow-hidden break-all mb-4\"\n              data-testid={`scheduledroom:name:${name}`}\n            >\n              {name}\n            </div>\n            <div className=\"flex gap-2\">\n              <AddToCalendar\n                event={{\n                  name,\n                  details: description,\n                  location: url,\n                  startsAt: dt.toISOString(),\n                  endsAt: new Date(\n                    dt.getTime() + 1 * 60 * 60 * 1000\n                  ).toISOString(),\n                }}\n                filename={name}\n              >\n                {(toggle) => (\n                  <BoxedIcon onClick={toggle}>\n                    <SolidCalendar />\n                  </BoxedIcon>\n                )}\n              </AddToCalendar>\n              {noCopyLinkButton ? null : (\n                <CopyScheduleRoomLinkButton text={url} />\n              )}\n              {isCreator && !noEditOrDeleteButton ? (\n                <>\n                  <BoxedIcon onClick={() => onEdit()}>\n                    <Edit size={18} />\n                  </BoxedIcon>\n                  <BoxedIcon\n                    onClick={() =>\n                      modalConfirm(\n                        \"Are you sure you want to delete this scheduled room?\",\n                        () => {\n                          mutateAsync([id]);\n                        }\n                      )\n                    }\n                  >\n                    <Trash size={18} />\n                  </BoxedIcon>\n                </>\n              ) : null}\n            </div>\n          </div>\n        </div>\n        <div className={`flex justify-between`}>\n          <div className=\"flex flex-col\">\n            <div className={`relative inline-flex mb-4`}>\n              <SingleUser size=\"xs\" src={creator.avatarUrl} />\n              <div\n                style={{\n                  display: \"-webkit-box\",\n                  WebkitBoxOrient: \"vertical\",\n                  WebkitLineClamp: 3,\n                }}\n                className={`ml-2 text-left flex-1`}\n              >\n                {creator.displayName}\n              </div>\n            </div>\n            <div className={`inline break-all`}>\n              <span className=\"text-accent\">\n                {isToday(dt)\n                  ? t(\"common.formattedIntlTime\", { time: dt })\n                  : t(\"common.formattedIntlDate\", { date: dt })}\n              </span>\n              {description ? ` | ` + description : ``}\n            </div>\n          </div>\n        </div>\n\n        {canStartRoom ? (\n          <div className={`flex mt-4`}>\n            {isCreator ? (\n              <Button\n                loading={isLoadingStartRoom}\n                onClick={() => {\n                  mutateAsyncStartRoom([\n                    {\n                      id,\n                      name,\n                      description,\n                    },\n                  ]);\n                }}\n              >\n                {t(\"modules.scheduledRooms.startRoom\")}\n              </Button>\n            ) : (\n              roomId && (\n                <Button\n                  loading={isLoadingStartRoom}\n                  onClick={() => {\n                    push(\"/room/\" + roomId);\n                  }}\n                >\n                  {t(\"common.joinRoom\")}\n                </Button>\n              )\n            )}\n          </div>\n        ) : null}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/ScheduledRoomsList.tsx",
    "content": "import { ScheduledRoom } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"../../ui/Button\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { FeedHeader } from \"../../ui/FeedHeader\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { CreateScheduleRoomModal } from \"./CreateScheduledRoomModal\";\nimport { EditScheduleRoomModalController } from \"./EditScheduleRoomModalController\";\nimport { ScheduledRoomCard } from \"./ScheduledRoomCard\";\n\ninterface ScheduledRoomsListProps {}\n\nconst Page = ({\n  onLoadMore,\n  cursor,\n  isLastPage,\n  isOnlyPage,\n  userId,\n  onEdit,\n}: {\n  onEdit: (sr: { scheduleRoomToEdit: ScheduledRoom; cursor: string }) => void;\n  userId: string;\n  cursor: string;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n  onLoadMore: (o: string) => void;\n}) => {\n  const { isLoading, data } = useTypeSafeQuery(\n    [\"getScheduledRooms\", cursor, \"all\", userId],\n    { staleTime: Infinity, refetchOnMount: \"always\" },\n    [cursor, \"all\", userId]\n  );\n  const update = useTypeSafeUpdateQuery();\n  const { t } = useTypeSafeTranslation();\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (!data) {\n    return null;\n  }\n\n  if (isOnlyPage && data.rooms.length === 0) {\n    return (\n      <div className={`mt-8 text-xl ml-4`}>\n        {t(\"modules.scheduledRooms.noneFound\")}\n      </div>\n    );\n  }\n\n  return (\n    <div className={`${isLastPage ? \"mb-24\" : \"\"}`}>\n      {data.rooms.map((r) => (\n        <div className={`mt-4`} key={r.id}>\n          <ScheduledRoomCard\n            onDeleteComplete={() => {\n              update([\"getScheduledRooms\", cursor, \"all\", userId], (d) => {\n                return {\n                  rooms: (d?.rooms || []).filter((x) => x.id !== r.id),\n                  nextCursor: d?.nextCursor,\n                };\n              });\n            }}\n            onEdit={() => onEdit({ cursor, scheduleRoomToEdit: r })}\n            info={r}\n          />\n        </div>\n      ))}\n      {isLastPage && data.nextCursor ? (\n        <div className={`flex justify-center my-4`}>\n          <Button size=\"small\" onClick={() => onLoadMore(data.nextCursor!)}>\n            {t(\"common.loadMore\")}\n          </Button>\n        </div>\n      ) : null}\n    </div>\n  );\n};\n\nexport const ScheduledRoomsList: React.FC<ScheduledRoomsListProps> = ({}) => {\n  const { t } = useTypeSafeTranslation();\n  const [open, setOpen] = useState(false);\n  const [{ cursors, userId }, setQueryState] = useState<{\n    cursors: string[];\n    userId: string;\n  }>({ cursors: [\"\"], userId: \"\" });\n  const update = useTypeSafeUpdateQuery();\n  const conn = useConn();\n\n  return (\n    <>\n      {open ? (\n        <CreateScheduleRoomModal\n          onScheduledRoom={(data, resp) => {\n            update([\"getScheduledRooms\", \"\", \"all\", userId], (d) => {\n              return {\n                rooms: [\n                  {\n                    roomId: null,\n                    creator: conn.user!,\n                    creatorId: conn.user!.id,\n                    description: data.description,\n                    id: resp.scheduledRoom.id,\n                    name: data.name,\n                    numAttending: 0,\n                    scheduledFor: data.scheduledFor.toISOString(),\n                  },\n                  ...(d?.rooms || []),\n                ],\n                nextCursor: d?.nextCursor,\n              };\n            });\n          }}\n          onRequestClose={() => setOpen(false)}\n        />\n      ) : null}\n      <MiddlePanel\n        stickyChildren={\n          <FeedHeader\n            actionTitle={t(\"modules.scheduledRooms.scheduleRoomHeader\")}\n            onActionClicked={() => {\n              setOpen(true);\n            }}\n            title={t(\"modules.scheduledRooms.title\")}\n          />\n        }\n      >\n        <EditScheduleRoomModalController\n          onScheduledRoom={(editInfo, data, _resp) => {\n            update([\"getScheduledRooms\", editInfo.cursor, userId], (d) => {\n              return {\n                rooms: (d?.rooms || []).map((x) =>\n                  x.id === editInfo.scheduleRoomToEdit.id\n                    ? {\n                        ...x,\n                        name: data.name,\n                        description: data.description,\n                        scheduledFor: data.scheduledFor.toISOString(),\n                      }\n                    : x\n                ),\n                nextCursor: d?.nextCursor,\n              };\n            });\n          }}\n        >\n          {({ onEdit }) =>\n            cursors.map((cursor, i) => (\n              <Page\n                userId={userId}\n                onLoadMore={(o) =>\n                  setQueryState({\n                    cursors: [...cursors, o],\n                    userId,\n                  })\n                }\n                onEdit={onEdit}\n                isOnlyPage={cursors.length === 1}\n                isLastPage={cursors.length - 1 === i}\n                key={cursor}\n                cursor={cursor}\n              />\n            ))\n          }\n        </EditScheduleRoomModalController>\n      </MiddlePanel>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/ScheduledRoomsPage.tsx",
    "content": "import React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { ScheduledRoomsList } from \"./ScheduledRoomsList\";\n\ninterface LoungePageProps {}\n\nexport const ScheduledRoomsPage: PageComponent<LoungePageProps> = ({}) => {\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title=\"Scheduled Rooms\" />\n      <DefaultDesktopLayout>\n        <ScheduledRoomsList />\n      </DefaultDesktopLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nScheduledRoomsPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/copyToClipboard.ts",
    "content": "import { showErrorToast } from \"../../lib/showErrorToast\";\n\n// https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript\nexport function copyTextToClipboard(text: string) {\n  const textArea = document.createElement(\"textarea\");\n  textArea.value = text;\n\n  // Avoid scrolling to bottom\n  textArea.style.top = \"0\";\n  textArea.style.left = \"0\";\n  textArea.style.position = \"fixed\";\n\n  document.body.appendChild(textArea);\n  textArea.focus();\n  textArea.select();\n\n  let good = true;\n\n  try {\n    good = document.execCommand(\"copy\");\n  } catch (err) {\n    console.error(err);\n    showErrorToast(err);\n    good = false;\n  }\n\n  document.body.removeChild(textArea);\n\n  return good;\n}\n"
  },
  {
    "path": "kibbeh/src/modules/scheduled-rooms/makeUrls.ts",
    "content": "export interface CalendarEvent {\n  name: string;\n  details: string | null;\n  location: string | null;\n  startsAt: string;\n  endsAt: string;\n}\n\nconst makeDuration = (event: CalendarEvent) => {\n  const minutes = Math.floor(\n    (+new Date(event.endsAt) - +new Date(event.startsAt)) / 60 / 1000\n  );\n  return `${`0${Math.floor(minutes / 60)}`.slice(-2)}${`0${minutes % 60}`.slice(\n    -2\n  )}`;\n};\n\nconst makeTime = (time: string) =>\n  new Date(time).toISOString().replace(/[-:]|\\.\\d{3}/g, \"\");\n\ntype Query = { [key: string]: null | boolean | number | string };\n\nconst makeUrl = (base: string, query: Query) =>\n  Object.keys(query).reduce((accum, key, index) => {\n    const value = query[key];\n\n    if (value !== null) {\n      return `${accum}${index === 0 ? \"?\" : \"&\"}${key}=${encodeURIComponent(\n        value\n      )}`;\n    }\n    return accum;\n  }, base);\n\nconst makeGoogleCalendarUrl = (event: CalendarEvent) =>\n  makeUrl(\"https://calendar.google.com/calendar/render\", {\n    action: \"TEMPLATE\",\n    dates: `${makeTime(event.startsAt)}/${makeTime(event.endsAt)}`,\n    location: event.location,\n    text: event.name,\n    details: event.details,\n  });\n\nconst makeOutlookCalendarUrl = (event: CalendarEvent) =>\n  makeUrl(\"https://outlook.live.com/owa\", {\n    rru: \"addevent\",\n    startdt: event.startsAt,\n    enddt: event.endsAt,\n    subject: event.name,\n    location: event.location,\n    body: event.details,\n    allday: false,\n    uid: new Date().getTime().toString(),\n    path: \"/calendar/view/Month\",\n  });\n\nconst makeYahooCalendarUrl = (event: CalendarEvent) =>\n  makeUrl(\"https://calendar.yahoo.com\", {\n    v: 60,\n    view: \"d\",\n    type: 20,\n    title: event.name,\n    st: makeTime(event.startsAt),\n    dur: makeDuration(event),\n    desc: event.details,\n    in_loc: event.location,\n  });\n\nconst makeICSCalendarUrl = (event: CalendarEvent) => {\n  const components = [\"BEGIN:VCALENDAR\", \"VERSION:2.0\", \"BEGIN:VEVENT\"];\n\n  // In case of SSR, document won't be defined\n  if (typeof document !== \"undefined\") {\n    components.push(`URL:${document.URL}`);\n  }\n\n  components.push(\n    `DTSTART:${makeTime(event.startsAt)}`,\n    `DTEND:${makeTime(event.endsAt)}`,\n    `SUMMARY:${event.name}`,\n    `DESCRIPTION:${event.details}`,\n    `LOCATION:${event.location}`,\n    \"END:VEVENT\",\n    \"END:VCALENDAR\"\n  );\n\n  return encodeURI(`data:text/calendar;charset=utf8,${components.join(\"\\n\")}`);\n};\n\ntype URLSet = { [key: string]: string };\n\nconst makeUrls = (event: CalendarEvent): URLSet => ({\n  google: makeGoogleCalendarUrl(event),\n  outlook: makeOutlookCalendarUrl(event),\n  yahoo: makeYahooCalendarUrl(event),\n  ics: makeICSCalendarUrl(event),\n});\n\nexport default makeUrls;\n"
  },
  {
    "path": "kibbeh/src/modules/search/SearchBarController.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { SearchBar } from \"../../ui/Search/SearchBar\";\nimport { SearchOverlay } from \"../../ui/Search/SearchOverlay\";\nimport Downshift from \"downshift\";\nimport { Room, User } from \"@dogehouse/kebab\";\nimport { useRouter } from \"next/router\";\nimport { useDebounce } from \"use-debounce\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport {\n  RoomSearchResult,\n  UserSearchResult,\n} from \"../../ui/Search/SearchResult\";\nimport { useMediaQuery } from \"react-responsive\";\nimport usePageVisibility from \"../../shared-hooks/usePageVisibility\";\n\ninterface SearchControllerProps {}\n\nexport const SearchBarController: React.FC<SearchControllerProps> = ({}) => {\n  const [rawText, setText] = useState(\"\");\n  const visible = usePageVisibility();\n  const [text] = useDebounce(rawText, 200);\n  const { t } = useTypeSafeTranslation();\n  const isOverflowing = useMediaQuery({ maxWidth: 475 });\n  let enabled = false;\n  const isUsernameSearch = text.startsWith(\"@\");\n\n  if (text && isUsernameSearch && text.trim().length > 2) {\n    enabled = true;\n  }\n  if (text && !isUsernameSearch && text.trim().length > 1) {\n    enabled = true;\n  }\n\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"search\", text],\n    {\n      enabled,\n    },\n    [text]\n  );\n  const { push } = useRouter();\n  const results = data ? [...data.rooms, ...data.users] : [];\n\n  return (\n    <Downshift<Room | User>\n      onChange={(selection) => {\n        if (!selection) {\n          return;\n        }\n        if (\"username\" in selection) {\n          push(`/u/[username]`, `/u/${selection.username}`);\n          return;\n        }\n\n        push(`/room/[id]`, `/room/${selection.id}`);\n      }}\n      onInputValueChange={(v) => {\n        if (visible) {\n          setText(v);\n        }\n      }}\n      itemToString={(item) => {\n        if (!item) {\n          return \"\";\n        } else if (\"username\" in item) {\n          return item.username;\n        }\n        return item.name;\n      }}\n    >\n      {({\n        getInputProps,\n        getItemProps,\n        getLabelProps,\n        getMenuProps,\n        isOpen,\n        inputValue,\n        highlightedIndex,\n        selectedItem,\n        getRootProps,\n      }) => (\n        <div className=\"relative w-full z-10 flex flex-col\">\n          <SearchBar\n            {...getInputProps()}\n            value={rawText}\n            placeholder={\n              isOverflowing\n                ? t(\"components.search.placeholderShort\")\n                : t(\"components.search.placeholder\")\n            }\n            isLoading={isLoading}\n          />\n          {isOpen ? (\n            <SearchOverlay\n              {...getRootProps({ refKey: \"ref\" }, { suppressRefError: true })}\n            >\n              <ul\n                className=\"w-full px-2 mb-2 mt-7 bg-primary-800 rounded-b-8 overflow-y-auto\"\n                {...getMenuProps({ style: { top: 0 } })}\n              >\n                {(data?.rooms.length === 0 && data?.users.length === 0) ||\n                !data ? (\n                  <InfoText className=\"p-3\">no results</InfoText>\n                ) : null}\n\n                {results.map((item, index) =>\n                  \"username\" in item ? (\n                    // eslint-disable-next-line react/jsx-key\n                    <li\n                      data-testid={`search:user:${item.username}`}\n                      {...getItemProps({\n                        key: item.id,\n                        index,\n                        item,\n                      })}\n                    >\n                      <UserSearchResult\n                        user={item}\n                        className={\n                          highlightedIndex === index\n                            ? \"bg-primary-700\"\n                            : \"bg-primary-800\"\n                        }\n                      />\n                    </li>\n                  ) : (\n                    <li\n                      {...getItemProps({\n                        key: item.id,\n                        index,\n                        item,\n                      })}\n                    >\n                      <RoomSearchResult\n                        room={item}\n                        className={\n                          highlightedIndex === index\n                            ? \"bg-primary-700\"\n                            : \"bg-primary-800\"\n                        }\n                      />\n                    </li>\n                  )\n                )}\n              </ul>\n            </SearchOverlay>\n          ) : null}\n        </div>\n      )}\n    </Downshift>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/search/SearchPage.tsx",
    "content": "import { Room, User } from \"@dogehouse/kebab\";\nimport router from \"next/router\";\nimport React, { useState } from \"react\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { SearchHeader } from \"../../ui/mobile/MobileHeader\";\nimport {\n  RoomSearchResult,\n  UserSearchResult,\n} from \"../../ui/Search/SearchResult\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\n\ninterface LoungePageProps {}\n\nexport const SearchPage: PageComponent<LoungePageProps> = ({}) => {\n  const screenType = useScreenType();\n  if (screenType !== \"fullscreen\") router.push(\"/dash\");\n\n  const [results, setResults] = useState([] as (User | Room)[]);\n  const [searchLoading, setSearchLoading] = useState(false);\n  const conn = useWrappedConn();\n\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title=\"Search\" />\n      <DefaultDesktopLayout\n        mobileHeader={\n          <SearchHeader\n            onSearchChange={(e) => {\n              console.log(e.target.value);\n              setSearchLoading(true);\n              conn.query.search(e.target.value).then((r) => {\n                setResults(r?.items);\n                setSearchLoading(false);\n              });\n            }}\n            searchPlaceholder=\"Search\"\n            onBackClick={() => router.back()}\n            searchLoading={searchLoading}\n          />\n        }\n      >\n        <div className=\"h-full w-full\">\n          {results &&\n            results.map((userOrRoom, i) => {\n              if (\"username\" in userOrRoom) {\n                return (\n                  <UserSearchResult\n                    onClick={() => router.push(`/u/${userOrRoom.username}`)}\n                    key={i}\n                    user={userOrRoom}\n                  />\n                );\n              } else {\n                return (\n                  <RoomSearchResult\n                    onClick={() => router.push(`/room/${userOrRoom.id}`)}\n                    key={i}\n                    room={userOrRoom}\n                  />\n                );\n              }\n            })}\n          {!results?.length && (\n            <InfoText className=\"pr-4 pl-5 py-3\">no results</InfoText>\n          )}\n        </div>\n      </DefaultDesktopLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nSearchPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/settings/OverlaySettingsPage.tsx",
    "content": "import { Formik } from \"formik\";\nimport isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport React, { useEffect } from \"react\";\nimport { object, string } from \"superstruct\";\nimport { InputField } from \"../../form-fields/InputField\";\nimport { useOverlayStore } from \"../../global-stores/useOverlayStore\";\nimport { validateStruct } from \"../../lib/validateStruct\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { Button } from \"../../ui/Button\";\nimport { OverlayKeybind } from \"../keyboard-shortcuts\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\n\ninterface OverlaySettingsProps {}\nconst overlaySettingsStruct = object({\n  appTitle: string(),\n});\nconst validateData = validateStruct(overlaySettingsStruct);\nconst isMac = process.platform === \"darwin\";\nexport const OverlaySettingsPage: PageComponent<OverlaySettingsProps> = () => {\n  const { appTitle } = useOverlayStore.getState();\n  const { push } = useRouter();\n  const { t } = useTypeSafeTranslation();\n  useEffect(() => {\n    if (!isElectron() || isMac) {\n      push(\"/dash\");\n    }\n  }, [push]);\n\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@rpc/page\", {\n        page: \"overlay-settings\",\n        opened: true,\n        modal: false,\n        data: \"\",\n      });\n      return () => {\n        ipcRenderer.send(\"@rpc/page\", {\n          page: \"overlay-settings\",\n          opened: false,\n          modal: false,\n          data: \"\",\n        });\n      };\n    }\n  }, []);\n\n  return (\n    <DefaultDesktopLayout>\n      <MiddlePanel>\n        <div className=\"flex flex-col text-primary-100\">\n          <OverlayKeybind className={`mb-4`} />\n          <Formik\n            initialValues={{\n              appTitle,\n            }}\n            validateOnChange={false}\n            validate={(values) => {\n              return validateData({\n                ...values,\n                appTitle: values.appTitle.trim(),\n              });\n            }}\n            onSubmit={(data) => {\n              useOverlayStore.getState().setData(data);\n            }}\n          >\n            {({ handleSubmit }) => (\n              <div className=\"flex\">\n                <InputField\n                  errorMsg={t(\"pages.overlaySettings.input.errorMsg\")}\n                  label={t(\"pages.overlaySettings.input.label\")}\n                  name=\"appTitle\"\n                />\n                <div className={`flex mt-12`}>\n                  <Button\n                    type=\"button\"\n                    onClick={() => handleSubmit()}\n                    className={`ml-2`}\n                  >\n                    {t(\"common.save\")}\n                  </Button>\n                </div>\n              </div>\n            )}\n          </Formik>\n        </div>\n      </MiddlePanel>\n    </DefaultDesktopLayout>\n  );\n};\n\nOverlaySettingsPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/settings/PrivacySettingForm.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport React, { useContext } from \"react\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { NativeSelect } from \"../../ui/NativeSelect\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\ninterface PrivacySettingFormProps {}\n\nexport const PrivacySettingForm: React.FC<PrivacySettingFormProps> = ({}) => {\n  const { mutateAsync } = useTypeSafeMutation(\"userUpdate\");\n  const { conn, setUser } = useContext(WebSocketContext);\n  const { user } = conn!;\n  const { t } = useTypeSafeTranslation();\n  return (\n    <div>\n      <div className=\"text-primary-100 mb-2\">\n        {t(\"pages.privacySettings.whispers.label\")}:\n      </div>\n      <div>\n        <NativeSelect\n          value={user.whisperPrivacySetting}\n          onChange={(e) => {\n            const whisperPrivacySetting = e.target\n              .value as User[\"whisperPrivacySetting\"];\n            setUser({ ...user, whisperPrivacySetting });\n            mutateAsync([{ whisperPrivacySetting }]);\n          }}\n        >\n          {[\n            t(\"pages.privacySettings.whispers.on\"),\n            t(\"pages.privacySettings.whispers.off\"),\n          ].map((v) => (\n            <option value={v} key={v}>\n              {v}&nbsp;&nbsp;&nbsp;\n            </option>\n          ))}\n        </NativeSelect>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/settings/PrivacySettingsPage.tsx",
    "content": "import React from \"react\";\nimport { HeaderController } from \"../../modules/display/HeaderController\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { PrivacySettingForm } from \"./PrivacySettingForm\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\ninterface ChatSettingsProps {}\n\nexport const PrivacySettingsPage: PageComponent<ChatSettingsProps> = () => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <DefaultDesktopLayout>\n      <HeaderController embed={{}} title={t(\"pages.privacySettings.title\")} />\n      <MiddlePanel>\n        <h1 className={`pb-4 text-4xl text-primary-100`}>\n          {t(\"pages.privacySettings.header\")}\n        </h1>\n\n        <PrivacySettingForm />\n      </MiddlePanel>\n    </DefaultDesktopLayout>\n  );\n};\n\nPrivacySettingsPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/settings/SoundEffectSettingsPage.tsx",
    "content": "import React, { useEffect } from \"react\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport {\n  useSoundEffectStore,\n  PossibleSoundEffect,\n} from \"../sound-effects/useSoundEffectStore\";\nimport { HeaderController } from \"../../modules/display/HeaderController\";\nimport isElectron from \"is-electron\";\nimport { PageComponent } from \"../../types/PageComponent\";\n\ninterface ChatSettingsProps {}\n\nconst capitalize = (s: string) =>\n  s.length ? s[0].toUpperCase() + s.slice(1) : s;\nconst camelToReg = (str: string) =>\n  str.replace(/[A-Z]/g, (letter) => ` ${letter}`);\n\nexport const SoundEffectSettings: PageComponent<ChatSettingsProps> = () => {\n  const [\n    soundEffectSettings,\n    setSetting,\n    playSoundEffect,\n  ] = useSoundEffectStore((x) => [x.settings, x.setSetting, x.playSoundEffect]);\n  const { t } = useTypeSafeTranslation();\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@rpc/page\", {\n        page: \"sound-effect-settings\",\n        opened: true,\n        modal: false,\n        data: \"\",\n      });\n      return () => {\n        ipcRenderer.send(\"@rpc/page\", {\n          page: \"sound-effect-settings\",\n          opened: false,\n          modal: false,\n          data: \"\",\n        });\n      };\n    }\n  }, []);\n  return (\n    <DefaultDesktopLayout>\n      <HeaderController\n        embed={{}}\n        title={t(\"pages.soundEffectSettings.title\")}\n      />\n      <MiddlePanel>\n        <h1 className={`pb-4 text-4xl text-primary-100`}>\n          {t(\"pages.soundEffectSettings.header\")}\n        </h1>\n\n        {Object.keys(soundEffectSettings).map((k) => {\n          return (\n            <div className={`flex mb-4 items-center`} key={k}>\n              <InfoText>{capitalize(camelToReg(k))}</InfoText>\n              <input\n                className=\"ml-2\"\n                type=\"checkbox\"\n                checked={soundEffectSettings[k as PossibleSoundEffect]}\n                onChange={() =>\n                  setSetting(\n                    k as PossibleSoundEffect,\n                    !soundEffectSettings[k as PossibleSoundEffect]\n                  )\n                }\n              />\n              <Button\n                size=\"small\"\n                onClick={() => playSoundEffect(k as PossibleSoundEffect, true)}\n                className={`ml-4`}\n              >\n                {t(\"pages.soundEffectSettings.playSound\")}\n              </Button>\n            </div>\n          );\n        })}\n      </MiddlePanel>\n    </DefaultDesktopLayout>\n  );\n};\n\nSoundEffectSettings.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/settings/VoiceSettingsPage.tsx",
    "content": "import React, { useEffect } from \"react\";\nimport { useGlobalVolumeStore } from \"../../global-stores/useGlobalVolumeStore\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { Button } from \"../../ui/Button\";\nimport { NativeSelect } from \"../../ui/NativeSelect\";\nimport { VolumeSlider } from \"../../ui/VolumeSlider\";\nimport {\n  MuteKeybind,\n  DeafKeybind,\n  PTTKeybind,\n  ChatKeybind,\n  InviteKeybind,\n  RequestToSpeakKeybind,\n} from \"../keyboard-shortcuts\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { useMicIdStore } from \"../webrtc/stores/useMicIdStore\";\nimport { HeaderController } from \"../../modules/display/HeaderController\";\nimport isElectron from \"is-electron\";\nimport { useDevices } from \"../../shared-hooks/useDevices\";\n\ninterface VoiceSettingsProps {}\n\nexport const VoiceSettingsPage: PageComponent<VoiceSettingsProps> = () => {\n  const { micId, setMicId } = useMicIdStore();\n  const { volume, set } = useGlobalVolumeStore();\n  const { devices, fetchMics } = useDevices();\n\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@rpc/page\", {\n        page: \"voice-settings\",\n        opened: true,\n        modal: false,\n        data: \"\",\n      });\n      return () => {\n        ipcRenderer.send(\"@rpc/page\", {\n          page: \"voice-settings\",\n          opened: false,\n          modal: false,\n          data: \"\",\n        });\n      };\n    }\n  }, []);\n\n  const { t } = useTypeSafeTranslation();\n\n  return (\n    <DefaultDesktopLayout>\n      <HeaderController embed={{}} title={t(\"pages.voiceSettings.title\")} />\n      <MiddlePanel>\n        <div className=\"flex flex-col text-primary-100\">\n          <div className={`flex mb-2`}>{t(\"pages.voiceSettings.mic\")} </div>\n          {devices.length ? (\n            <NativeSelect\n              className={`mb-4`}\n              value={micId}\n              onChange={(e) => setMicId(e.target.value)}\n            >\n              {devices.map(({ id, label }) => (\n                <option key={id} value={id}>\n                  {label}\n                </option>\n              ))}\n            </NativeSelect>\n          ) : (\n            <div className={`flex mb-4`}>\n              {t(\"pages.voiceSettings.permissionError\")}\n            </div>\n          )}\n          <div className=\"flex\">\n            <Button\n              size=\"small\"\n              onClick={() => {\n                fetchMics();\n              }}\n            >\n              {t(\"pages.voiceSettings.refresh\")}\n            </Button>\n          </div>\n          <div className={`flex mt-8 mb-2`}>\n            {t(\"pages.voiceSettings.volume\")}{\" \"}\n          </div>\n          <div className={`flex mb-8`}>\n            <VolumeSlider\n              volume={volume}\n              onVolume={(n) => set({ volume: n })}\n            />\n          </div>\n          <MuteKeybind className={`mb-4`} />\n          <DeafKeybind className={`mb-4`} />\n          <PTTKeybind className={`mb-4`} />\n          <ChatKeybind className={`mb-4`} />\n          <InviteKeybind className={`mb-4`} />\n          <RequestToSpeakKeybind />\n        </div>\n      </MiddlePanel>\n    </DefaultDesktopLayout>\n  );\n};\n\nVoiceSettingsPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/sound-effects/SoundEffectPlayer.tsx",
    "content": "import React, { createContext } from \"react\";\nimport { soundEffects, useSoundEffectStore } from \"./useSoundEffectStore\";\n\nconst soundKeys = Object.keys(soundEffects);\n\nexport const SoundEffectContext = createContext<{\n  playSoundEffect: (name: keyof typeof soundEffects) => void;\n}>({ playSoundEffect: () => {} });\n\nexport const SoundEffectPlayer: React.FC = ({}) => {\n  const add = useSoundEffectStore((x) => x.add);\n\n  return (\n    <>\n      {soundKeys.map((key) => (\n        <audio\n          preload=\"none\"\n          controls={false}\n          key={key}\n          ref={(ref) => {\n            if (ref) {\n              ref.volume = 0.7;\n              add(key, ref);\n            }\n          }}\n          src={`/sound-effects/${\n            soundEffects[key as keyof typeof soundEffects]\n          }`}\n        />\n      ))}\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/sound-effects/useSoundEffectStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const soundEffects = {\n  roomChatMention: \"roomChatMention.wav\",\n  unmute: \"unmute.wav\",\n  mute: \"mute.wav\",\n  roomInvite: \"roomInvite.wav\",\n  deafen: \"deafen.wav\",\n  undeafen: \"undeafen.wav\",\n};\n\nexport type PossibleSoundEffect = keyof typeof soundEffects;\n\nconst keyToLocalStorageKey = (s: string) => `@sound-effect/${s}`;\n\nfunction getInitialSettings() {\n  const soundEffectSettings: Record<PossibleSoundEffect, boolean> = {\n    roomChatMention: true,\n    unmute: true,\n    mute: true,\n    roomInvite: true,\n    deafen: true,\n    undeafen: true,\n  };\n\n  try {\n    Object.keys(soundEffects).forEach((key) => {\n      const v = localStorage.getItem(keyToLocalStorageKey(key)) || \"\";\n      soundEffectSettings[key as PossibleSoundEffect] = !v || v === \"true\";\n    });\n  } catch {}\n\n  return soundEffectSettings;\n}\n\nexport const useSoundEffectStore = create(\n  combine(\n    {\n      audioRefMap: {} as Record<string, HTMLAudioElement>,\n      settings: getInitialSettings(),\n    },\n    (set, get) => ({\n      setSetting: (key: PossibleSoundEffect, value: boolean) => {\n        try {\n          localStorage.setItem(keyToLocalStorageKey(key), value.toString());\n        } catch {}\n        set((x) => ({\n          settings: { ...x.settings, [key]: value },\n        }));\n      },\n      playSoundEffect: (se: keyof typeof soundEffects, force = false) => {\n        const { audioRefMap, settings } = get();\n        if (force || settings[se]) {\n          audioRefMap[se]?.play();\n        }\n      },\n      add: (key: string, audio: HTMLAudioElement) =>\n        set((s) => ({ audioRefMap: { ...s.audioRefMap, [key]: audio } })),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/user/EditProfileModal.tsx",
    "content": "import { Form, Formik } from \"formik\";\nimport isElectron from \"is-electron\";\nimport React, { useContext, useEffect } from \"react\";\nimport { object, pattern, size, string, optional } from \"superstruct\";\nimport { InputField } from \"../../form-fields/InputField\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\nimport { validateStruct } from \"../../lib/validateStruct\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../../ui/Button\";\nimport { ButtonLink } from \"../../ui/ButtonLink\";\nimport { Modal } from \"../../ui/Modal\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\n\nconst profileStruct = object({\n  displayName: size(string(), 2, 50),\n  username: pattern(string(), /^(\\w){4,15}$/),\n  bio: size(string(), 0, 160),\n  avatarUrl: pattern(\n    string(),\n    /^https?:\\/\\/(www\\.|)((a|p)bs.twimg.com\\/(profile_images|sticky\\/default_profile_images)\\/(.*)\\.(jpg|png|jpeg|webp)|avatars\\.githubusercontent\\.com\\/u\\/[^\\s]+|github.com\\/identicons\\/[^\\s]+|cdn.discordapp.com\\/avatars\\/[^\\s]+\\/[^\\s]+\\.(jpg|png|jpeg|webp))/\n  ),\n  bannerUrl: optional(\n    pattern(\n      string(),\n      /^https?:\\/\\/(www\\.|)(pbs.twimg.com\\/profile_banners\\/(.+)\\/(.+)(?:\\.(jpg|png|jpeg|webp))?|avatars\\.githubusercontent\\.com\\/u\\/)/\n    )\n  ),\n});\n\ninterface EditProfileModalProps {\n  isOpen: boolean;\n  onRequestClose: () => void;\n  onEdit?: (data: {\n    displayName: string;\n    username: string;\n    bio: string;\n    avatarUrl: string;\n  }) => void;\n}\n\nconst validateFn = validateStruct(profileStruct);\n\nexport const EditProfileModal: React.FC<EditProfileModalProps> = ({\n  isOpen,\n  onRequestClose,\n  onEdit,\n}) => {\n  const { conn, setUser } = useContext(WebSocketContext);\n  const { mutateAsync } = useTypeSafeMutation(\"editProfile\");\n  const { t } = useTypeSafeTranslation();\n\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@rpc/page\", {\n        page: \"edit-profile\",\n        opened: isOpen,\n        modal: true,\n        data: \"\",\n      });\n    }\n  }, [isOpen]);\n\n  if (!conn) {\n    return null;\n  }\n\n  const { user } = conn;\n\n  return (\n    <Modal isOpen={isOpen} onRequestClose={onRequestClose}>\n      {isOpen ? (\n        <Formik\n          initialValues={{\n            displayName: user.displayName,\n            username: user.username,\n            bio: user.bio || \"\",\n            avatarUrl: user.avatarUrl,\n            bannerUrl: user.bannerUrl || \"\",\n          }}\n          validateOnChange={false}\n          validate={(values) => {\n            return validateFn({\n              ...values,\n              bannerUrl: values.bannerUrl || undefined,\n              displayName: values.displayName.trim(),\n            });\n          }}\n          onSubmit={async (data) => {\n            const { isUsernameTaken } = await mutateAsync([data]);\n            if (isUsernameTaken) {\n              showErrorToast(\n                t(\"components.modals.editProfileModal.usernameTaken\")\n              );\n            } else {\n              if (conn) {\n                setUser({\n                  ...conn?.user,\n                  ...data,\n                  bio: data.bio.trim(),\n                  displayName: data.displayName.trim(),\n                });\n              }\n              onEdit?.(data);\n              onRequestClose();\n            }\n          }}\n        >\n          {({ isSubmitting }) => (\n            <Form className={`flex-col w-full`}>\n              <h4 className={`mb-2 text-primary-100`}>\n                {t(\"pages.viewUser.editProfile\")}\n              </h4>\n              <InputField\n                className={`mb-4`}\n                errorMsg={t(\n                  \"components.modals.editProfileModal.avatarUrlError\"\n                )}\n                label={t(\"components.modals.editProfileModal.avatarUrlLabel\")}\n                name=\"avatarUrl\"\n              />\n              <InputField\n                className={`mb-4`}\n                errorMsg={t(\n                  \"components.modals.editProfileModal.avatarUrlError\"\n                )}\n                label={t(\"components.modals.editProfileModal.bannerUrlLabel\")}\n                name=\"bannerUrl\"\n              />\n              <InputField\n                className={`mb-4`}\n                errorMsg={t(\n                  \"components.modals.editProfileModal.displayNameError\"\n                )}\n                label={t(\"components.modals.editProfileModal.displayNameLabel\")}\n                name=\"displayName\"\n              />\n              <InputField\n                className={`mb-4`}\n                errorMsg={t(\"components.modals.editProfileModal.usernameError\")}\n                label={t(\"components.modals.editProfileModal.usernameLabel\")}\n                name=\"username\"\n              />\n              <InputField\n                className={`mb-4`}\n                errorMsg={t(\"components.modals.editProfileModal.bioError\")}\n                label={t(\"components.modals.editProfileModal.bioLabel\")}\n                textarea\n                name=\"bio\"\n              />\n              <div className={`flex pt-2 items-center`}>\n                <Button loading={isSubmitting} type=\"submit\" className={`mr-3`}>\n                  {t(\"common.save\")}\n                </Button>\n                <ButtonLink type=\"button\" onClick={onRequestClose}>\n                  {t(\"common.cancel\")}\n                </ButtonLink>\n              </div>\n            </Form>\n          )}\n        </Formik>\n      ) : null}\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/user/FollowingController.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { SolidFriends } from \"../../icons\";\nimport { isServer } from \"../../lib/isServer\";\nimport { ApiPreloadLink } from \"../../shared-components/ApiPreloadLink\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useIntersectionObserver } from \"../../shared-hooks/useIntersectionObserver\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"../../ui/Button\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { Spinner } from \"../../ui/Spinner\";\nimport { SingleUser } from \"../../ui/UserAvatar\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\n\ninterface FollowingControllerProps {}\n\nconst Page = ({\n  cursor,\n  isLastPage,\n  onLoadMore,\n  username,\n  isFollowing,\n}: {\n  isFollowing: boolean;\n  username: string;\n  cursor: number;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n  onLoadMore: (o: number) => void;\n}) => {\n  const conn = useConn();\n  const { setNode, entry } = useIntersectionObserver({});\n  const {\n    mutateAsync,\n    isLoading: followLoading,\n    variables,\n  } = useTypeSafeMutation(\"follow\");\n\n  const { t } = useTypeSafeTranslation();\n  const updater = useTypeSafeUpdateQuery();\n  const vars: [string, boolean, number] = [username, isFollowing, cursor];\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"getFollowList\", ...vars],\n    {\n      enabled: !!username && !isServer,\n      staleTime: Infinity,\n      refetchOnMount: \"always\",\n    },\n    vars\n  );\n\n  const [shouldLoadMore, setShouldLoadMore] = useState(false);\n\n  useEffect(() => {\n    setShouldLoadMore(!!entry?.isIntersecting);\n  }, [entry?.isIntersecting]);\n\n  useEffect(() => {\n    if (shouldLoadMore && data?.nextCursor) {\n      onLoadMore(data.nextCursor!);\n      setShouldLoadMore(false);\n    }\n  }, [data?.nextCursor, entry?.isIntersecting, onLoadMore, shouldLoadMore]);\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (!data || data.users.length === 0) {\n    const styles = \"text-primary-200 text-center\";\n    if (isFollowing) return <div className={styles}>{t(\"pages.followList.followingNone\")}</div>;\n    else return <div className={styles}>{t(\"pages.followList.noFollowers\")}</div>;\n  }\n\n  return (\n    <>\n      {data.users.map((user) => (\n        <div key={user.id} className=\"flex items-center mb-6\">\n          <div className=\"flex\">\n            <SingleUser size=\"md\" src={user.avatarUrl} />\n          </div>\n          <div className=\"flex px-4 flex-1\">\n            <ApiPreloadLink route=\"profile\" data={{ username: user.username }}>\n              <div className=\"flex flex-col w-full\">\n                <div className=\"block max-w-md text-primary-100 truncate w-full\">\n                  {user.displayName}\n                </div>\n                <div className=\"flex text-primary-200\">@{user.username}</div>\n              </div>\n            </ApiPreloadLink>\n          </div>\n          <div className=\"flex\">\n            {conn.user.username !== user.username && (\n              <Button\n                loading={followLoading && variables?.[0] === user.id}\n                onClick={async () => {\n                  await mutateAsync([user.id, !user.youAreFollowing]);\n                  updater([\"getFollowList\", ...vars], (x) =>\n                    !x\n                      ? x\n                      : {\n                          ...x,\n                          users: x.users.map((u) =>\n                            u.id === user.id\n                              ? {\n                                  ...u,\n                                  numFollowers:\n                                    u.numFollowers +\n                                    (user.youAreFollowing ? -1 : 1),\n                                  youAreFollowing: !user.youAreFollowing,\n                                }\n                              : u\n                          ),\n                        }\n                  );\n                }}\n                size=\"small\"\n                color={user.youAreFollowing ? \"secondary\" : \"primary\"}\n                icon={user.youAreFollowing ? null : <SolidFriends />}\n              >\n                {user.youAreFollowing\n                  ? t(\"pages.viewUser.unfollow\")\n                  : t(\"pages.viewUser.followHim\")}\n              </Button>\n            )}\n          </div>\n        </div>\n      ))}\n      {isLastPage && data.nextCursor && (\n        <div ref={setNode} className={`flex justify-center py-5`}>\n          <Spinner />\n        </div>\n      )}\n    </>\n  );\n};\n\nexport const FollowingController: React.FC<FollowingControllerProps> = ({}) => {\n  const { pathname, query } = useRouter();\n  const isFollowing = pathname.includes(\"/following\");\n  const username = typeof query.username === \"string\" ? query.username : \"\";\n  const [cursors, setCursors] = useState([0]);\n\n  return (\n    <MiddlePanel>\n      <div className=\"flex flex-col mb-6\">\n        {cursors.map((cursor, i) => (\n          <Page\n            username={username}\n            isFollowing={isFollowing}\n            key={cursor}\n            cursor={cursor}\n            isOnlyPage={cursors.length === 1}\n            onLoadMore={(c) => setCursors([...cursors, c])}\n            isLastPage={i === cursors.length - 1}\n          />\n        ))}\n      </div>\n    </MiddlePanel>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/user/FollowingOnlinePage.tsx",
    "content": "import React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { FollowingOnlineController } from \"../dashboard/FollowingOnlineController\";\nimport { ProfileBlockController } from \"../dashboard/ProfileBlockController\";\nimport { LeftPanel, MiddlePanel, RightPanel } from \"../layouts/GridPanels\";\nimport { HeaderController } from \"../../modules/display/HeaderController\";\nimport { MainLayout } from \"../layouts/MainLayout\";\nimport { FollowersOnlineWrapper } from \"../../ui/FollowersOnline\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\ninterface UserPageProps {}\n\nexport const FollowingOnlinePage: PageComponent<UserPageProps> = ({}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title={t(\"pages.followingOnlineList.title\")} />\n      <MainLayout\n        leftPanel={<FollowingOnlineController />}\n        rightPanel={<ProfileBlockController />}\n      >\n        <div className=\"mt-4\">\n          <FollowingOnlineController></FollowingOnlineController>\n        </div>\n      </MainLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nFollowingOnlinePage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/user/FollowingPage.tsx",
    "content": "import React from \"react\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { FollowingOnlineController } from \"../dashboard/FollowingOnlineController\";\nimport { ProfileBlockController } from \"../dashboard/ProfileBlockController\";\nimport { LeftPanel, MiddlePanel, RightPanel } from \"../layouts/GridPanels\";\nimport { FollowingController } from \"./FollowingController\";\nimport { UserProfileController } from \"./UserProfileController\";\nimport { HeaderController } from \"../../modules/display/HeaderController\";\nimport { MainLayout } from \"../layouts/MainLayout\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\n\ninterface UserPageProps {}\n\nexport const FollowingPage: PageComponent<UserPageProps> = ({}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <WaitForWsAndAuth>\n      <HeaderController embed={{}} title={t(\"pages.followList.title\")} />\n      <MainLayout\n        leftPanel={<FollowingOnlineController />}\n        rightPanel={<ProfileBlockController />}\n      >\n        <FollowingController />\n      </MainLayout>\n    </WaitForWsAndAuth>\n  );\n};\n\nFollowingPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/user/UserPage.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { apiBaseUrl } from \"../../lib/constants\";\nimport { PageComponent } from \"../../types/PageComponent\";\nimport { HeaderController } from \"../display/HeaderController\";\nimport { DefaultDesktopLayout } from \"../layouts/DefaultDesktopLayout\";\nimport { MiddlePanel } from \"../layouts/GridPanels\";\nimport { UserProfileController } from \"./UserProfileController\";\n\ninterface UserPageProps {\n  username: string;\n  user: User | null;\n}\n\nexport const UserPage: PageComponent<UserPageProps> = ({ username, user }) => {\n  return (\n    <>\n      {user ? (\n        <HeaderController\n          title={user.displayName}\n          embed={{ image: user.avatarUrl }}\n          description={user.bio ? user.bio : undefined}\n        />\n      ) : (\n        <HeaderController />\n      )}\n      <DefaultDesktopLayout>\n        <MiddlePanel>\n          <UserProfileController key={username} />\n        </MiddlePanel>\n      </DefaultDesktopLayout>\n    </>\n  );\n};\n\nUserPage.getInitialProps = async ({ query }) => {\n  const username = typeof query.username === \"string\" ? query.username : \"\";\n  try {\n    const res = await fetch(`${apiBaseUrl}/user/${username}`);\n    const { user }: { user: User | null } = await res.json();\n    return { username, user };\n  } catch {\n    return { username, user: null };\n  }\n};\n\nUserPage.ws = true;\n"
  },
  {
    "path": "kibbeh/src/modules/user/UserProfileController.tsx",
    "content": "import isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport React, { useEffect, useState } from \"react\";\nimport { isServer } from \"../../lib/isServer\";\nimport { usePreloadPush } from \"../../shared-components/ApiPreloadLink\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"../../ui/Button\";\nimport { CenterLoader } from \"../../ui/CenterLoader\";\nimport { InfoText } from \"../../ui/InfoText\";\nimport { UserProfile } from \"../../ui/UserProfile\";\nimport { EditProfileModal } from \"./EditProfileModal\";\nimport { VerticalUserInfoWithFollowButton } from \"./VerticalUserInfoWithFollowButton\";\n\ninterface UserProfileControllerProps {}\n\nconst isMac = process.platform === \"darwin\";\n\nexport const UserProfileController: React.FC<UserProfileControllerProps> = ({}) => {\n  const conn = useConn();\n  const { t } = useTypeSafeTranslation();\n  const { push } = useRouter();\n  const { query } = useRouter();\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"getUserProfile\", query.username as string],\n    {\n      enabled:\n        typeof query.username === \"string\" && !!query.username && !isServer,\n      refetchOnMount: \"always\",\n    },\n    [query.username as string]\n  );\n\n  // commented this out as rn this shows up all the time\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@rpc/page\", {\n        page: \"profile\",\n        opened: true,\n        modal: false,\n        data: query.username,\n      });\n      return () => {\n        ipcRenderer.send(\"@rpc/page\", {\n          page: \"profile\",\n          opened: false,\n          modal: false,\n          data: query.username,\n        });\n      };\n    }\n  }, [query]);\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (!data || (\"error\" in data && data.error.includes(\"could not find\"))) {\n    return <InfoText>{t(\"pages.myProfile.couldNotFindUser\")}</InfoText>;\n  } else if (\"error\" in data && data.error.includes(\"blocked\")) {\n    return <InfoText>You have been blocked by this user.</InfoText>;\n  } else if (\"error\" in data) {\n    return <InfoText>{data.error}</InfoText>;\n  }\n\n  return (\n    <>\n      <UserProfile user={data} isCurrentUser={data.id === conn.user.id} />\n      {data.id === conn.user.id && (\n        <div className={`pt-6 pb-6 flex`}>\n          <Button\n            style={{ marginRight: \"10px\" }}\n            size=\"small\"\n            onClick={() => push(`/voice-settings`)}\n          >\n            {t(\"pages.myProfile.voiceSettings\")}\n          </Button>\n          {isElectron() && !isMac ? (\n            <Button\n              style={{ marginRight: \"10px\" }}\n              size=\"small\"\n              onClick={() => push(`/overlay-settings`)}\n            >\n              {t(\"pages.myProfile.overlaySettings\")}\n            </Button>\n          ) : null}\n          <Button\n            style={{ marginRight: \"10px\" }}\n            size=\"small\"\n            onClick={() => push(`/sound-effect-settings`)}\n          >\n            {t(\"pages.myProfile.soundSettings\")}\n          </Button>\n          <Button size=\"small\" onClick={() => push(`/privacy-settings`)}>\n            {t(\"pages.myProfile.privacySettings\")}\n          </Button>\n        </div>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/user/VerticalUserInfoWithFollowButton.tsx",
    "content": "import { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { SolidFriends, SolidFriendsAdd } from \"../../icons\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"../../ui/Button\";\nimport { VerticalUserInfo } from \"../../ui/VerticalUserInfo\";\n\ninterface VerticalUserInfoControllerProps {\n  user: UserWithFollowInfo;\n  idOrUsernameUsedForQuery: string;\n}\n\nexport const VerticalUserInfoWithFollowButton: React.FC<VerticalUserInfoControllerProps> = ({\n  idOrUsernameUsedForQuery,\n  user,\n}) => {\n  const { mutateAsync, isLoading: followLoading } = useTypeSafeMutation(\n    \"follow\"\n  );\n  const conn = useConn();\n  const updater = useTypeSafeUpdateQuery();\n  const { t } = useTypeSafeTranslation();\n\n  return (\n    <>\n      <VerticalUserInfo user={user} />\n      <div className={`flex mb-5 items-center w-full justify-center`}>\n        {/* @todo add real icon */}\n        {user.id !== conn.user.id ? (\n          <Button\n            loading={followLoading}\n            onClick={async () => {\n              await mutateAsync([user.id, !user.youAreFollowing]);\n              updater([\"getUserProfile\", idOrUsernameUsedForQuery], (u) =>\n                !u\n                  ? u\n                  : {\n                      ...u,\n                      numFollowers:\n                        (u as UserWithFollowInfo).numFollowers +\n                        (user.youAreFollowing ? -1 : 1),\n                      youAreFollowing: !user.youAreFollowing,\n                    }\n              );\n            }}\n            size=\"small\"\n            color={user.youAreFollowing ? \"secondary\" : \"primary\"}\n            icon={user.youAreFollowing ? null : <SolidFriendsAdd />}\n          >\n            {user.youAreFollowing\n              ? t(\"pages.viewUser.unfollow\")\n              : t(\"pages.viewUser.followHim\")}\n          </Button>\n        ) : null}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/WebRtcApp.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React, { useContext, useEffect, useRef } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { ActiveSpeakerListener } from \"./components/ActiveSpeakerListener\";\nimport { AudioRender } from \"./components/AudioRender\";\nimport { useMicIdStore } from \"./stores/useMicIdStore\";\nimport { useVoiceStore } from \"./stores/useVoiceStore\";\nimport { consumeAudio } from \"./utils/consumeAudio\";\nimport { createTransport } from \"./utils/createTransport\";\nimport { joinRoom } from \"./utils/joinRoom\";\nimport { receiveVoice } from \"./utils/receiveVoice\";\nimport { sendVoice } from \"./utils/sendVoice\";\n\ninterface App2Props {}\n\nexport function closeVoiceConnections(_roomId: string | null) {\n  const { roomId, mic, nullify } = useVoiceStore.getState();\n  if (_roomId === null || _roomId === roomId) {\n    if (mic) {\n      console.log(\"stopping mic\");\n      mic.stop();\n    }\n\n    console.log(\"nulling transports\");\n    nullify();\n  }\n}\n\nexport const WebRtcApp: React.FC<App2Props> = () => {\n  const { conn } = useContext(WebSocketContext);\n  const { mic } = useVoiceStore();\n  const { micId } = useMicIdStore();\n  const { muted } = useMuteStore();\n  const { deafened } = useDeafStore();\n  const { setCurrentRoomId } = useCurrentRoomIdStore();\n  const initialLoad = useRef(true);\n  const { push } = useRouter();\n\n  useEffect(() => {\n    if (micId && !initialLoad.current) {\n      sendVoice();\n    }\n    initialLoad.current = false;\n  }, [micId]);\n  const consumerQueue = useRef<{ roomId: string; d: any }[]>([]);\n\n  function flushConsumerQueue(_roomId: string) {\n    try {\n      for (const {\n        roomId,\n        d: { peerId, consumerParameters },\n      } of consumerQueue.current) {\n        if (_roomId === roomId) {\n          consumeAudio(consumerParameters, peerId);\n        }\n      }\n    } catch (err) {\n      console.log(err);\n    } finally {\n      consumerQueue.current = [];\n    }\n  }\n  useEffect(() => {\n    if (mic) {\n      mic.enabled = !muted && !deafened;\n    }\n  }, [mic, muted, deafened]);\n  useEffect(() => {\n    if (!conn) {\n      return;\n    }\n\n    const unsubs = [\n      conn.addListener<any>(\"you_left_room\", (d) => {\n        if (d.kicked) {\n          const { currentRoomId } = useCurrentRoomIdStore.getState();\n          if (currentRoomId !== d.roomId) {\n            return;\n          }\n          setCurrentRoomId(null);\n          closeVoiceConnections(d.roomId);\n          push(\"/dash\");\n        }\n      }),\n      conn.addListener<any>(\"new-peer-speaker\", async (d) => {\n        const { roomId, recvTransport } = useVoiceStore.getState();\n        if (recvTransport && roomId === d.roomId) {\n          await consumeAudio(d.consumerParameters, d.peerId);\n        } else {\n          consumerQueue.current = [...consumerQueue.current, { roomId, d }];\n        }\n      }),\n      conn.addListener<any>(\"you-are-now-a-speaker\", async (d) => {\n        if (d.roomId !== useVoiceStore.getState().roomId) {\n          return;\n        }\n        // setStatus(\"connected-speaker\");\n        try {\n          await createTransport(conn, d.roomId, \"send\", d.sendTransportOptions);\n        } catch (err) {\n          console.log(err);\n          return;\n        }\n        console.log(\"sending voice\");\n        try {\n          await sendVoice();\n        } catch (err) {\n          console.log(err);\n        }\n      }),\n      conn.addListener<any>(\"you-joined-as-peer\", async (d) => {\n        closeVoiceConnections(null);\n        useVoiceStore.getState().set({ roomId: d.roomId });\n        // setStatus(\"connected-listener\");\n        consumerQueue.current = [];\n        console.log(\"creating a device\");\n        try {\n          await joinRoom(d.routerRtpCapabilities);\n        } catch (err) {\n          console.log(\"error creating a device | \", err);\n          return;\n        }\n        try {\n          await createTransport(conn, d.roomId, \"recv\", d.recvTransportOptions);\n        } catch (err) {\n          console.log(\"error creating recv transport | \", err);\n          return;\n        }\n        receiveVoice(conn, () => flushConsumerQueue(d.roomId));\n      }),\n      conn.addListener<any>(\"you-joined-as-speaker\", async (d) => {\n        closeVoiceConnections(null);\n        useVoiceStore.getState().set({ roomId: d.roomId });\n        // setStatus(\"connected-speaker\");\n        consumerQueue.current = [];\n        console.log(\"creating a device\");\n        try {\n          await joinRoom(d.routerRtpCapabilities);\n        } catch (err) {\n          console.log(\"error creating a device | \", err);\n          return;\n        }\n        try {\n          await createTransport(conn, d.roomId, \"send\", d.sendTransportOptions);\n        } catch (err) {\n          console.log(\"error creating send transport | \", err);\n          return;\n        }\n        console.log(\"sending voice\");\n        try {\n          await sendVoice();\n        } catch (err) {\n          console.log(\"error sending voice | \", err);\n          return;\n        }\n        await createTransport(conn, d.roomId, \"recv\", d.recvTransportOptions);\n        receiveVoice(conn, () => flushConsumerQueue(d.roomId));\n      }),\n    ];\n\n    return () => {\n      unsubs.forEach((x) => x());\n    };\n  }, [conn, push, setCurrentRoomId]);\n\n  return (\n    <>\n      <AudioRender />\n      <ActiveSpeakerListener />\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/components/ActiveSpeakerListener.tsx",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport hark from \"hark\";\nimport React, { useContext, useEffect } from \"react\";\nimport { useCurrentRoomIdStore } from \"../../../global-stores/useCurrentRoomIdStore\";\nimport { useDebugAudioStore } from \"../../../global-stores/useDebugAudio\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { WebSocketContext } from \"../../ws/WebSocketProvider\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\ninterface ActiveSpeakerListenerProps {}\n\nexport const ActiveSpeakerListener: React.FC<ActiveSpeakerListenerProps> = ({}) => {\n  const { conn } = useContext(WebSocketContext);\n  const { micStream } = useVoiceStore();\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const { debugAudio } = useDebugAudioStore();\n  useEffect(() => {\n    if (!currentRoomId || !micStream || !conn) {\n      return;\n    }\n\n    const wrappedConn = wrap(conn);\n\n    const harker = hark(micStream, { threshold: -65, interval: 75 });\n\n    harker.on(\"speaking\", () => {\n      wrappedConn.mutation.speakingChange(true);\n    });\n\n    harker.on(\"stopped_speaking\", () => {\n      wrappedConn.mutation.speakingChange(false);\n    });\n\n    return () => {\n      harker.stop();\n    };\n  }, [micStream, currentRoomId, conn]);\n\n  return null;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/components/AudioRender.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { useGlobalVolumeStore } from \"../../../global-stores/useGlobalVolumeStore\";\nimport { Button } from \"../../../ui/Button\";\nimport { useConsumerStore } from \"../stores/useConsumerStore\";\nimport { useDeafStore } from \"../../../global-stores/useDeafStore\";\ninterface AudioRenderProps {}\n\nconst MyAudio = ({\n  volume,\n  onRef,\n  ...props\n}: React.DetailedHTMLProps<\n  React.AudioHTMLAttributes<HTMLAudioElement>,\n  HTMLAudioElement\n> & {\n  onRef: (a: HTMLAudioElement) => void;\n  volume: number;\n}) => {\n  const myRef = useRef<HTMLAudioElement>(null);\n  useEffect(() => {\n    if (myRef.current) {\n      myRef.current.volume = volume;\n    }\n  }, [volume]);\n  return (\n    <audio\n      ref={(r) => {\n        if (r && !myRef.current) {\n          (myRef as any).current = r;\n          onRef(r);\n        }\n      }}\n      {...props}\n    />\n  );\n};\n\nexport const AudioRender: React.FC<AudioRenderProps> = () => {\n  const notAllowedErrorCountRef = useRef(0);\n  const [showAutoPlayModal, setShowAutoPlayModal] = useState(false);\n  const { volume: globalVolume } = useGlobalVolumeStore();\n  const { consumerMap, setAudioRef } = useConsumerStore();\n  const audioRefs = useRef<[string, HTMLAudioElement][]>([]);\n  const { deafened } = useDeafStore();\n\n  return (\n    <>\n      <div\n        className={`absolute top-0 w-full h-full flex z-50 bg-primary-900 ${\n          showAutoPlayModal ? \"\" : \"hidden\"\n        }`}\n      >\n        <div className={`flex p-8 rounded m-auto bg-primary-700 flex-col`}>\n          <div className={`flex text-center mb-4 text-primary-100`}>\n            Browsers require user interaction before they will play audio. Just\n            click okay to continue.\n          </div>\n          <Button\n            onClick={() => {\n              setShowAutoPlayModal(false);\n              audioRefs.current.forEach(([_, a]) => {\n                a.play().catch((err) => {\n                  console.warn(err);\n                });\n              });\n            }}\n          >\n            okay\n            {Object.keys(consumerMap).map((k) => {\n              const { consumer, volume: userVolume } = consumerMap[k];\n              return (\n                <MyAudio\n                  volume={\n                    deafened ? 0 : (userVolume / 200) * (globalVolume / 100)\n                  }\n                  // autoPlay\n                  playsInline\n                  controls={false}\n                  key={consumer.id}\n                  onRef={(a) => {\n                    setAudioRef(k, a);\n                    audioRefs.current.push([k, a]);\n                    a.srcObject = new MediaStream([consumer.track]);\n                    // prevent modal from showing up more than once in a single render cycle\n                    const notAllowedErrorCount =\n                      notAllowedErrorCountRef.current;\n                    a.play().catch((error) => {\n                      if (\n                        error.name === \"NotAllowedError\" &&\n                        notAllowedErrorCountRef.current === notAllowedErrorCount\n                      ) {\n                        notAllowedErrorCountRef.current++;\n                        setShowAutoPlayModal(true);\n                      }\n                      console.warn(\"audioElem.play() failed:%o\", error);\n                    });\n                  }}\n                />\n              );\n            })}\n          </Button>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/components/MicPicker.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { useMicIdStore } from \"../stores/useMicIdStore\";\n\ninterface MicPickerProps {}\n\nexport const MicPicker: React.FC<MicPickerProps> = () => {\n  const { micId, setMicId } = useMicIdStore();\n  const [options, setOptions] = useState<\n    Array<{ id: string; label: string } | null>\n  >([]);\n  useEffect(() => {\n    navigator.mediaDevices\n      .enumerateDevices()\n      .then((x) =>\n        setOptions(\n          x.map((y) =>\n            y.kind !== \"audioinput\" ? null : { id: y.deviceId, label: y.label }\n          )\n        )\n      );\n  }, []);\n  return (\n    <>\n      {options.length === 0 ? (\n        <div className=\"flex\">no mics available</div>\n      ) : null}\n      {options.length ? (\n        <select\n          value={micId}\n          onChange={(e) => {\n            const id = e.target.value;\n            setMicId(id);\n          }}\n        >\n          {options.map((x) =>\n            !x ? null : (\n              <option key={x.id} value={x.id}>\n                {x.label}\n              </option>\n            )\n          )}\n        </select>\n      ) : null}\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useAskForMicStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAskForMicStore = create(\n  combine(\n    {\n      hasAsked: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useAudioTracks.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAudioTracks = create(\n  combine(\n    {\n      tracks: [] as MediaStreamTrack[],\n    },\n    (set) => ({\n      add: (track: MediaStreamTrack) =>\n        set((s) => ({ tracks: [...s.tracks, track] })),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useConsumerStore.ts",
    "content": "import { Consumer } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useConsumerStore = create(\n  combine(\n    {\n      consumerMap: {} as Record<\n        string,\n        { consumer: Consumer; volume: number; audioRef?: HTMLAudioElement }\n      >,\n    },\n    (set) => ({\n      setAudioRef: (userId: string, audioRef: HTMLAudioElement) => {\n        set((s) => {\n          if (userId in s.consumerMap) {\n            return {\n              consumerMap: {\n                ...s.consumerMap,\n                [userId]: {\n                  ...s.consumerMap[userId],\n                  audioRef,\n                },\n              },\n            };\n          }\n\n          console.log(\"could not find consumer for \", userId);\n          return s;\n        });\n      },\n      setVolume: (userId: string, volume: number) => {\n        set((s) =>\n          userId in s.consumerMap\n            ? {\n                consumerMap: {\n                  ...s.consumerMap,\n                  [userId]: {\n                    ...s.consumerMap[userId],\n                    volume,\n                  },\n                },\n              }\n            : s\n        );\n      },\n      add: (c: Consumer, userId: string) =>\n        set((s) => {\n          let volume = 100;\n          if (userId in s.consumerMap) {\n            const x = s.consumerMap[userId];\n            volume = x.volume;\n            x.consumer.close();\n          }\n          return {\n            consumerMap: {\n              ...s.consumerMap,\n              [userId]: { consumer: c, volume },\n            },\n          };\n        }),\n      closeAll: () =>\n        set((s) => {\n          Object.values(s.consumerMap).forEach(\n            ({ consumer: c }) => !c.closed && c.close()\n          );\n          return {\n            consumerMap: {},\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useMicIdStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const MIC_KEY = \"micId\";\n\nconst getInitialState = () => {\n  try {\n    return localStorage.getItem(MIC_KEY) || \"\";\n  } catch {\n    return \"\";\n  }\n};\n\nexport const useMicIdStore = create(\n  combine(\n    {\n      micId: getInitialState(),\n    },\n    (set) => ({\n      setMicId: (id: string) => {\n        try {\n          localStorage.setItem(MIC_KEY, id);\n        } catch {}\n        set({ micId: id });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useMicPermErrorStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useMicPermErrorStore = create(\n  combine(\n    {\n      error: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useProducerStore.ts",
    "content": "import { Producer } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useProducerStore = create(\n  combine(\n    {\n      producer: null as Producer | null,\n    },\n    (set) => ({\n      add: (p: Producer) =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n          return { producer: p };\n        }),\n      close: () =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n          return {\n            producer: null,\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useSocketStatus.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype State =\n  | \"auth-good\"\n  | \"open\"\n  | \"connecting\"\n  | \"closed-by-server\"\n  | \"closed\";\n\nexport const useSocketStatus = create(\n  combine(\n    {\n      status: \"connecting\" as State,\n    },\n    (set) => ({\n      setStatus: (status: State) => set({ status }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useStatus.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype State =\n  | \"init\"\n  | \"ws-disconnected\"\n  | \"voice-server-disconnected\"\n  | \"connected-no-room\"\n  | \"connected-listener\"\n  | \"bad-auth\"\n  | \"killed\"\n  | \"connected-speaker\";\n\nexport const useStatus = create(\n  combine(\n    {\n      status: \"init\" as State,\n    },\n    (set) => ({\n      setStatus: (status: State) => set({ status }),\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/stores/useVoiceStore.ts",
    "content": "import { Device } from \"mediasoup-client\";\nimport { detectDevice, Transport } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const getDevice = () => {\n  try {\n    let handlerName = detectDevice();\n    if (!handlerName) {\n      console.warn(\n        \"mediasoup does not recognize this device, so ben has defaulted it to Chrome74\"\n      );\n      handlerName = \"Chrome74\";\n    }\n    return new Device({ handlerName });\n  } catch {\n    return null;\n  }\n};\n\nexport const useVoiceStore = create(\n  combine(\n    {\n      roomId: \"\",\n      micStream: null as MediaStream | null,\n      mic: null as MediaStreamTrack | null,\n      recvTransport: null as Transport | null,\n      sendTransport: null as Transport | null,\n      device: getDevice(),\n    },\n    (set) => ({\n      nullify: () =>\n        set({\n          recvTransport: null,\n          sendTransport: null,\n          roomId: \"\",\n          mic: null,\n          micStream: null,\n        }),\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/types.ts",
    "content": "export type AddWsListenerOnce = (op: string, fn: (d: any) => void) => void;\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/utils/consumeAudio.ts",
    "content": "import { useConsumerStore } from \"../stores/useConsumerStore\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport const consumeAudio = async (consumerParameters: any, peerId: string) => {\n  const { recvTransport } = useVoiceStore.getState();\n  if (!recvTransport) {\n    console.log(\"skipping consumeAudio because recvTransport is null\");\n    return false;\n  }\n  const consumer = await recvTransport.consume({\n    ...consumerParameters,\n    appData: {\n      peerId,\n      producerId: consumerParameters.producerId,\n      mediaTag: \"cam-audio\",\n    },\n  });\n\n  useConsumerStore.getState().add(consumer, peerId);\n\n  return true;\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/utils/createTransport.ts",
    "content": "import { Connection } from \"@dogehouse/kebab/lib/websocket/raw\";\nimport { TransportOptions } from \"mediasoup-client/lib/types\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport async function createTransport(\n  conn: Connection,\n  _roomId: string,\n  direction: \"recv\" | \"send\",\n  transportOptions: TransportOptions\n) {\n  console.log(`create ${direction} transport`);\n  const { device, set } = useVoiceStore.getState();\n\n  // ask the server to create a server-side transport object and send\n  // us back the info we need to create a client-side transport\n  console.log(\"transport options\", transportOptions);\n  const transport =\n    direction === \"recv\"\n      ? await device!.createRecvTransport(transportOptions)\n      : await device!.createSendTransport(transportOptions);\n\n  // mediasoup-client will emit a connect event when media needs to\n  // start flowing for the first time. send dtlsParameters to the\n  // server, then call callback() on success or errback() on failure.\n  transport.on(\"connect\", ({ dtlsParameters }, callback, errback) => {\n    conn.once<any>(`@connect-transport-${direction}-done`, (d: { error: string | null }) => {\n      if (d.error) {\n        console.log(`connect-transport ${direction} failed`, d.error);\n        if (d.error.includes(\"already called\")) {\n          callback();\n        } else {\n          errback();\n        }\n      } else {\n        console.log(`connect-transport ${direction} success`);\n        callback();\n      }\n    });\n    conn.send(`@connect-transport`, {\n      transportId: transportOptions.id,\n      dtlsParameters,\n      direction,\n    });\n  });\n\n  if (direction === \"send\") {\n    // sending transports will emit a produce event when a new track\n    // needs to be set up to start sending. the producer's appData is\n    // passed as a parameter\n    transport.on(\n      \"produce\",\n      ({ kind, rtpParameters, appData }, callback, errback) => {\n        console.log(\"transport produce event\", appData.mediaTag);\n        // we may want to start out paused (if the checkboxes in the ui\n        // aren't checked, for each media type. not very clean code, here\n        // but, you know, this isn't a real application.)\n        // let paused = false;\n        // if (appData.mediaTag === \"cam-video\") {\n        //   paused = getCamPausedState();\n        // } else if (appData.mediaTag === \"cam-audio\") {\n        //   paused = getMicPausedState();\n        // }\n        // tell the server what it needs to know from us in order to set\n        // up a server-side producer object, and get back a\n        // producer.id. call callback() on success or errback() on\n        // failure.\n        conn.once<any>(`@send-track-${direction}-done`, (d: { error: string | null, id: string }) => {\n          if (d.error) {\n            console.log(`send-track ${direction} failed`, d.error);\n            errback();\n          } else {\n            console.log(`send-track-transport ${direction} success`);\n            callback({ id: d.id });\n          }\n        });\n\n        conn.send(\"@send-track\", {\n          transportId: transportOptions.id,\n          kind,\n          rtpParameters,\n          rtpCapabilities: device!.rtpCapabilities,\n          paused: false,\n          appData,\n          direction,\n        });\n      }\n    );\n  }\n\n  // for this simple demo, any time a transport transitions to closed,\n  // failed, or disconnected, leave the room and reset\n  //\n  transport.on(\"connectionstatechange\", (state) => {\n    console.log(\n      `${direction} transport ${transport.id} connectionstatechange ${state}`\n    );\n  });\n\n  if (direction === \"recv\") {\n    set({ recvTransport: transport });\n  } else {\n    set({ sendTransport: transport });\n  }\n}\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/utils/joinRoom.ts",
    "content": "import { RtpCapabilities } from \"mediasoup-client/lib/types\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport const joinRoom = async (routerRtpCapabilities: RtpCapabilities) => {\n  const { device } = useVoiceStore.getState();\n  if (!device!.loaded) {\n    await device!.load({ routerRtpCapabilities });\n  }\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/utils/mergeRoomPermission.ts",
    "content": "import { RoomPermissions } from \"@dogehouse/kebab\";\n\nexport const mergeRoomPermission = (\n  currentRoomPermission: RoomPermissions | null | undefined,\n  newRoomPermissions: Partial<RoomPermissions>\n) => {\n  return {\n    ...(currentRoomPermission || {\n      askedToSpeak: false,\n      isMod: false,\n      isSpeaker: false,\n    }),\n    ...newRoomPermissions,\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/utils/receiveVoice.ts",
    "content": "import { RoomPeer } from \"@dogehouse/kebab\";\nimport { Connection } from \"@dogehouse/kebab/lib/websocket/raw\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\nimport { consumeAudio } from \"./consumeAudio\";\n\nexport const receiveVoice = (conn: Connection, flushQueue: () => void) => {\n  conn.once(\"@get-recv-tracks-done\", (params: { consumerParametersArr: RoomPeer[] }) => {\n    try {\n      for (const { peerId, consumerParameters } of params.consumerParametersArr) {\n        consumeAudio(consumerParameters, peerId);\n      }\n    } catch (err) {\n      console.log(err);\n    } finally {\n      flushQueue();\n    }\n  });\n  conn.send(\"@get-recv-tracks\", {\n    rtpCapabilities: useVoiceStore.getState().device!.rtpCapabilities,\n  });\n};\n"
  },
  {
    "path": "kibbeh/src/modules/webrtc/utils/sendVoice.ts",
    "content": "import { useMicIdStore } from \"../stores/useMicIdStore\";\nimport { useMicPermErrorStore } from \"../stores/useMicPermErrorStore\";\nimport { useProducerStore } from \"../stores/useProducerStore\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport const sendVoice = async () => {\n  const { micId } = useMicIdStore.getState();\n  const { sendTransport, set, mic } = useVoiceStore.getState();\n  if (!sendTransport) {\n    console.log(\"no sendTransport in sendVoice\");\n    return;\n  }\n  mic?.stop();\n  // eslint-disable-next-line init-declarations\n  let micStream: MediaStream;\n  try {\n    micStream = await navigator.mediaDevices.getUserMedia({\n      audio: micId ? { deviceId: micId } : true,\n    });\n    useMicPermErrorStore.getState().set({ error: false });\n  } catch (err) {\n    set({ mic: null, micStream: null });\n    console.log(err);\n    useMicPermErrorStore.getState().set({ error: true });\n    return;\n  }\n\n  const audioTracks = micStream.getAudioTracks();\n\n  if (audioTracks.length) {\n    console.log(\"creating producer...\");\n    const track = audioTracks[0];\n    useProducerStore.getState().add(\n      await sendTransport.produce({\n        track,\n        appData: { mediaTag: \"cam-audio\" },\n      })\n    );\n    set({ mic: track, micStream });\n    return;\n  }\n\n  set({ mic: null, micStream: null });\n};\n"
  },
  {
    "path": "kibbeh/src/modules/ws/WebSocketProvider.tsx",
    "content": "import React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { raw, User } from \"@dogehouse/kebab\";\nimport { useTokenStore } from \"../auth/useTokenStore\";\nimport { apiBaseUrl } from \"../../lib/constants\";\nimport { useRouter } from \"next/router\";\nimport { showErrorToast } from \"../../lib/showErrorToast\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useDeafStore } from \"../../global-stores/useDeafStore\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useVoiceStore } from \"../webrtc/stores/useVoiceStore\";\nimport { closeVoiceConnections } from \"../webrtc/WebRtcApp\";\n\ninterface WebSocketProviderProps {\n  shouldConnect: boolean;\n}\n\ntype V = raw.Connection | null;\n\nexport const WebSocketContext = React.createContext<{\n  conn: V;\n  setUser: (u: User) => void;\n  setConn: (u: raw.Connection | null) => void;\n}>({\n  conn: null,\n  setUser: () => {},\n  setConn: () => {},\n});\n\nexport const WebSocketProvider: React.FC<WebSocketProviderProps> = ({\n  shouldConnect,\n  children,\n}) => {\n  const hasTokens = useTokenStore((s) => s.accessToken && s.refreshToken);\n  const [conn, setConn] = useState<V>(null);\n  const { replace } = useRouter();\n  const isConnecting = useRef(false);\n\n  useEffect(() => {\n    if (!conn && shouldConnect && hasTokens && !isConnecting.current) {\n      isConnecting.current = true;\n      raw\n        .connect(\"\", \"\", {\n          waitToReconnect: true,\n          url: apiBaseUrl.replace(\"http\", \"ws\") + \"/socket\",\n          getAuthOptions: () => {\n            const { accessToken, refreshToken } = useTokenStore.getState();\n            const { recvTransport, sendTransport } = useVoiceStore.getState();\n\n            const reconnectToVoice = !recvTransport\n              ? true\n              : recvTransport.connectionState !== \"connected\" ||\n                sendTransport?.connectionState !== \"connected\";\n\n            console.log({\n              reconnectToVoice,\n              recvState: recvTransport?.connectionState,\n              sendState: sendTransport?.connectionState,\n            });\n\n            return {\n              accessToken,\n              refreshToken,\n              reconnectToVoice,\n              currentRoomId: useCurrentRoomIdStore.getState().currentRoomId,\n              muted: useMuteStore.getState().muted,\n              deafened: useDeafStore.getState().deafened,\n            };\n          },\n          onConnectionTaken: () => {\n            closeVoiceConnections(null);\n            useCurrentRoomIdStore.getState().setCurrentRoomId(null);\n            replace(\"/connection-taken\");\n          },\n          onClearTokens: () => {\n            console.log(\"clearing tokens...\");\n            useTokenStore\n              .getState()\n              .setTokens({ accessToken: \"\", refreshToken: \"\" });\n            setConn(null);\n            closeVoiceConnections(null);\n            useCurrentRoomIdStore.getState().setCurrentRoomId(null);\n            replace(\"/logout\");\n          },\n        })\n        .then((x) => {\n          setConn(x);\n          if (x.user.currentRoomId) {\n            useCurrentRoomIdStore\n              .getState()\n              // if an id exists already, that means they are trying to join another room\n              // just let them join the other room rather than overwriting it\n              .setCurrentRoomId((id) => id || x.user.currentRoomId!);\n          }\n        })\n        .catch((err) => {\n          if (err.code === 4001) {\n            replace(`/?next=${window.location.pathname}`);\n          }\n        })\n        .finally(() => {\n          isConnecting.current = false;\n        });\n    }\n  }, [conn, shouldConnect, hasTokens, replace]);\n\n  useEffect(() => {\n    if (!conn) {\n      return;\n    }\n\n    return conn.addListener<{\n      refreshToken: string;\n      accessToken: string;\n    }>(\"new-tokens\", ({ refreshToken, accessToken }) => {\n      useTokenStore.getState().setTokens({\n        accessToken,\n        refreshToken,\n      });\n    });\n  }, [conn]);\n\n  return (\n    <WebSocketContext.Provider\n      value={useMemo(\n        () => ({\n          conn,\n          setConn,\n          setUser: (u: User) => {\n            if (conn) {\n              setConn({\n                ...conn,\n                user: u,\n              });\n            }\n          },\n        }),\n        [conn]\n      )}\n    >\n      {children}\n    </WebSocketContext.Provider>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/pages/404.tsx",
    "content": "import { useEffect } from \"react\";\nimport { useRouter } from \"next/router\";\n\nexport default function Custom404() {\n  const router = useRouter();\n  useEffect(() => {\n    router.replace(\"/dash\");\n  });\n  return null;\n}\n"
  },
  {
    "path": "kibbeh/src/pages/_app.tsx",
    "content": "import React, { useEffect } from \"react\";\nimport \"../styles/globals.css\";\nimport \"../styles/electron-header.css\";\nimport \"../styles/banner-button.css\";\nimport \"../styles/date-time-picker.css\";\nimport { AppProps } from \"next/app\";\nimport { QueryClientProvider } from \"react-query\";\nimport { WebSocketProvider } from \"../modules/ws/WebSocketProvider\";\nimport { PageComponent } from \"../types/PageComponent\";\nimport { queryClient } from \"../lib/queryClient\";\nimport { isServer } from \"../lib/isServer\";\nimport { init_i18n } from \"../lib/i18n\";\nimport { SoundEffectPlayer } from \"../modules/sound-effects/SoundEffectPlayer\";\nimport ReactModal from \"react-modal\";\nimport { ErrorToastController } from \"../modules/errors/ErrorToastController\";\nimport { WebRtcApp } from \"../modules/webrtc/WebRtcApp\";\nimport { MainWsHandlerProvider } from \"../shared-hooks/useMainWsHandler\";\nimport NProgress from \"nprogress\";\nimport Router from \"next/router\";\nimport \"nprogress/nprogress.css\";\nimport { KeybindListener } from \"../modules/keyboard-shortcuts/KeybindListener\";\nimport { InvitedToJoinRoomModal } from \"../shared-components/InvitedToJoinRoomModal\";\nimport { ConfirmModal } from \"../shared-components/ConfirmModal\";\nimport isElectron from \"is-electron\";\nimport Head from \"next/head\";\nimport { useHostStore } from \"../global-stores/useHostStore\";\n\nif (!isServer) {\n  init_i18n();\n}\n\nRouter.events.on(\"routeChangeStart\", () => {\n  NProgress.start();\n});\nRouter.events.on(\"routeChangeComplete\", () => NProgress.done());\nRouter.events.on(\"routeChangeError\", () => NProgress.done());\n\nReactModal.setAppElement(\"#__next\");\n\nfunction App({ Component, pageProps }: AppProps) {\n  // keep this here as long as this version is still in dev.\n  // baklava listens to this event to re-size it's window\n  useEffect(() => {\n    if (isElectron()) {\n      const ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@dogehouse/loaded\", \"kibbeh\");\n      ipcRenderer.send(\"@app/hostPlatform\");\n      ipcRenderer.on(\n        \"@app/hostPlatform\",\n        (\n          event: any,\n          platform: { isLinux: boolean; isWin: boolean; isMac: boolean }\n        ) => {\n          useHostStore.getState().setData(platform);\n        }\n      );\n      document.documentElement.style.setProperty(\n        \"--screen-height-reduction\",\n        \"38px\"\n      );\n    }\n  }, []);\n\n  if (\n    isServer &&\n    !Component.getInitialProps &&\n    (Component as PageComponent<unknown>).ws\n  ) {\n    return null;\n  }\n\n  return (\n    <WebSocketProvider\n      shouldConnect={!!(Component as PageComponent<unknown>).ws}\n    >\n      <QueryClientProvider client={queryClient}>\n        <MainWsHandlerProvider>\n          <Head>\n            <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" />\n            <link rel=\"manifest\" href=\"/manifest.json\" />\n            <meta\n              name=\"viewport\"\n              content=\"width=device-width, initial-scale=1, user-scalable=no, user-scalable=0\"\n            />\n            <link rel=\"apple-touch-icon\" href=\"/img/doge.png\"></link>\n            <link rel=\"apple-touch-startup-image\" href=\"img/doge512.png\" />\n          </Head>\n          <Component {...pageProps} />\n          <SoundEffectPlayer />\n          <ErrorToastController />\n          <WebRtcApp />\n          <KeybindListener />\n          <InvitedToJoinRoomModal />\n          <ConfirmModal />\n        </MainWsHandlerProvider>\n      </QueryClientProvider>\n    </WebSocketProvider>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "kibbeh/src/pages/admin.tsx",
    "content": "import { AdminPage } from \"../modules/admin/AdminPage\";\n\nexport default AdminPage;\n"
  },
  {
    "path": "kibbeh/src/pages/connection-taken.tsx",
    "content": "import React from \"react\";\nimport { HeaderController } from \"../modules/display/HeaderController\";\nimport { ElectronHeader } from \"../modules/layouts/ElectronHeader\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../ui/Button\";\n\ninterface ConnectionTakenProps {}\n\nconst ConnectionTaken: React.FC<ConnectionTakenProps> = ({}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <>\n      <HeaderController title=\"Connection Taken\" embed={{}} />\n      <div className=\"flex w-full h-full flex-col items-center justify-center p-8\">\n        <ElectronHeader />\n        <h4 className=\"text-primary-100 mb-4\">\n          {t(\"components.wsKilled.description\")}\n        </h4>\n        <Button\n          onClick={() => {\n            window.location.href = window.location.origin + \"/dash\";\n          }}\n        >\n          {t(\"components.wsKilled.reconnect\")}\n        </Button>\n      </div>\n    </>\n  );\n};\n\nexport default ConnectionTaken;\n"
  },
  {
    "path": "kibbeh/src/pages/dash.tsx",
    "content": "import { DashboardPage } from \"../modules/dashboard/DashboardPage\";\n\nexport default DashboardPage;\n"
  },
  {
    "path": "kibbeh/src/pages/developer/bots/edit/[username]/index.tsx",
    "content": "import { BotsEditPage } from \"../../../../../modules/developer/BotsEditPage\";\n\nexport default BotsEditPage;\n"
  },
  {
    "path": "kibbeh/src/pages/developer/bots/index.tsx",
    "content": "import { BotsPage } from \"../../../modules/developer/BotsPage\";\n\nexport default BotsPage;\n"
  },
  {
    "path": "kibbeh/src/pages/download.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../ui/Button\";\nimport { HeaderController } from \"../modules/display/HeaderController\";\nimport { LgLogo } from \"../icons\";\n\nconst links = [\n  \"https://github.com/benawad/dogehouse/releases/download/{{tag}}/DogeHouse-Setup-{{version}}.exe\", // windows\n  \"https://github.com/benawad/dogehouse/releases/download/{{tag}}/DogeHouse-{{version}}.dmg\", // macOS\n  \"https://github.com/benawad/dogehouse/releases/download/{{tag}}/DogeHouse-{{version}}.AppImage\", // linux\n  \"https://github.com/benawad/dogehouse/releases/download/{{tag}}/dogehouse_{{version}}_amd64.deb\", // linux deb\n  \"https://github.com/benawad/dogehouse/releases/download/{{tag}}/DogeHouse-{{version}}.tar.gz\", // linux targz\n  \"https://github.com/benawad/dogehouse/releases/download/{{tag}}/DogeHouse-{{version}}.x86_64.rpm\", // linux rpm\n];\n\nconst platforms = [\"Windows\", \"macOS\", \"Linux\", \"Linux\", \"Linux\", \"Linux\"]; // Sorted respectively\n\nconst extentions = [\".exe\", \".dmg\", \".AppImage\", \".deb\", \".tar.gz\", \".rpm\"];\n\nfunction getOS() {\n  let isWindows = false;\n  let isMac = false;\n  let isLinux = false;\n  let isPhone = false;\n\n  const userAgent = window.navigator.userAgent;\n  const platform = window.navigator.platform;\n  const macosPlatforms = [\n    \"Macintosh\",\n    \"MacIntel\",\n    \"MacPPC\",\n    \"Mac68K\",\n    \"darwin\",\n  ];\n  const windowsPlatforms = [\"Win32\", \"Win64\", \"Windows\", \"WinCE\"];\n  const iosPlatforms = [\"iPhone\", \"iPad\", \"iPod\"];\n\n  let os = \"Windows\";\n  isWindows = true;\n  if (macosPlatforms.indexOf(platform) !== -1) {\n    os = \"macOS\";\n    isMac = true;\n  } else if (iosPlatforms.indexOf(platform) !== -1) {\n    os = \"iOS\";\n    isPhone = true;\n  } else if (windowsPlatforms.indexOf(platform) !== -1) {\n    os = \"Windows\";\n    isWindows = true;\n  } else if (/Android/.test(userAgent)) {\n    os = \"Android\";\n    isPhone = true;\n  } else if (/Linux/.test(platform)) {\n    os = \"Linux\";\n    isLinux = true;\n  }\n\n  return { isWindows, isMac, isLinux, isPhone, os };\n}\n\nfunction OtherPlatformButton(props: {\n  platform: string;\n  currentPlatform: number;\n  downloadLinks: string[];\n  linuxed: number;\n}) {\n  const { t } = useTypeSafeTranslation();\n  const index = platforms.indexOf(props.platform);\n  const isCurrent = index === props.currentPlatform;\n  let add = 0;\n  if (platforms[index] === \"Linux\") {\n    add = props.linuxed;\n  }\n  return !isCurrent ? (\n    <Button\n      color=\"secondary\"\n      className=\"my-2\"\n      onClick={() => {\n        window.location.href = props.downloadLinks[index + add];\n      }}\n    >\n      {t(\"pages.download.download_for\")\n        .replace(\"%platform%\", platforms[index + add])\n        .replace(\"%ext%\", extentions[index + add])}\n    </Button>\n  ) : null;\n}\n\nfunction CurrentPlatformButton(props: {\n  platform: number;\n  downloadLinks: string[];\n  linuxed: number;\n}) {\n  const { t } = useTypeSafeTranslation();\n  const plat = platforms[props.platform];\n  let add = 0;\n  if (plat === \"Linux\") {\n    add = props.linuxed;\n  }\n  return (\n    <Button\n      onClick={() => {\n        window.location.href = props.downloadLinks[props.platform + add];\n      }}\n    >\n      {t(\"pages.download.download_for\")\n        .replace(\"%platform%\", plat)\n        .replace(\"%ext%\", extentions[props.platform + add])}\n    </Button>\n  );\n}\n\nexport default function Download() {\n  const [loaded, setLoaded] = useState(false);\n  const [downloadLinks, setDownloadLinks] = useState(links);\n  const [currentPlatform, setCurrentPlatform] = useState(0);\n  const [downloadFailed, setDownloadFailed] = useState(false);\n\n  let linuxed = -1;\n\n  const { t } = useTypeSafeTranslation();\n\n  useEffect(() => {\n    const os = getOS();\n    if (!os.isPhone) {\n      let res = false;\n      const xmlHttp = new XMLHttpRequest();\n      xmlHttp.open(\n        \"GET\",\n        \"https://api.github.com/repos/benawad/dogehouse/releases/latest\"\n      );\n      xmlHttp.send(null);\n      xmlHttp.onreadystatechange = () => {\n        if (xmlHttp.responseText && !res) {\n          try {\n            const data = JSON.parse(xmlHttp.responseText);\n            res = true;\n            const tag = data.tag_name;\n            if (tag) {\n              const version = tag.replace(\"v\", \"\");\n              links.forEach((l) => {\n                const i = links.indexOf(l);\n                links[i] = l\n                  .replace(\"{{tag}}\", tag)\n                  .replace(\"{{version}}\", version);\n              });\n              setDownloadLinks(links);\n              setCurrentPlatform(platforms.indexOf(os.os));\n              setLoaded(true);\n              setDownloadFailed(false);\n            } else {\n              setLoaded(true);\n              setDownloadFailed(true);\n            }\n          } catch (e) {\n            res = false;\n            setDownloadFailed(true);\n          }\n        }\n      };\n    } else {\n      setLoaded(true);\n      setDownloadFailed(true);\n    }\n  }, []);\n\n  let text = \"\";\n\n  if (!loaded) {\n    text = t(\"common.loading\");\n  } else if (downloadFailed) {\n    text = t(\"pages.download.failed\");\n  } else {\n    text = t(\"pages.download.prompt\");\n  }\n\n  let button = null;\n\n  if (loaded) {\n    if (downloadFailed) {\n      button = (\n        <Button\n          onClick={() => {\n            window.location.href =\n              \"https://github.com/benawad/dogehouse/releases/latest\";\n          }}\n        >\n          {t(\"pages.download.visit_gh\")}\n        </Button>\n      );\n    } else {\n      button = (\n        <div className=\"flex lg:flex-row md:flex-col sm:flex-col lg:space-x-4 p-2 items-center\">\n          {platforms.map((platform) => {\n            if (platform === platforms[currentPlatform]) {\n              if (platform === \"Linux\") linuxed++;\n              return (\n                <CurrentPlatformButton\n                  platform={currentPlatform}\n                  downloadLinks={downloadLinks}\n                  linuxed={linuxed}\n                />\n              );\n            } else {\n              return null;\n            }\n          })}\n        </div>\n      );\n    }\n  }\n\n  return (\n    <>\n      <HeaderController title=\"Download\" />\n      <div className=\"flex w-full h-full flex-col items-center justify-center p-8\">\n        <h4 className=\"text-primary-100 mb-4\">{text}</h4>\n\n        {button}\n\n        {!loaded || downloadFailed ? null : (\n          <div className=\"flex lg:flex-row md:flex-col sm:flex-col lg:space-x-4 p-2 items-center\">\n            {platforms &&\n              platforms.map((platform) => {\n                if (platform === \"Linux\") linuxed++;\n                return (\n                  <OtherPlatformButton\n                    platform={platform}\n                    currentPlatform={currentPlatform}\n                    downloadLinks={downloadLinks}\n                    linuxed={linuxed}\n                    key={`${platform}-${linuxed}`}\n                  />\n                );\n              })}\n          </div>\n        )}\n      </div>\n\n      <div className=\"flex flex-row absolute bottom-0 w-full justify-between px-5 py-5 mt-auto items-center sm:px-7\">\n            <LgLogo onClick={() => {\n                window.location.href =\n                  \"https://dogehouse.tv\";\n              }}/>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "kibbeh/src/pages/index.tsx",
    "content": "import { LoginPage } from \"../modules/landing-page/LoginPage\";\n//\nexport default LoginPage;\n"
  },
  {
    "path": "kibbeh/src/pages/language.tsx",
    "content": "import { LanguagePage } from \"../modules/language/LanguagePage\";\n\nexport default LanguagePage;\n"
  },
  {
    "path": "kibbeh/src/pages/logout.tsx",
    "content": "import { useRouter } from \"next/router\";\nimport React, { useEffect } from \"react\";\nimport { useTokenStore } from \"../modules/auth/useTokenStore\";\nimport { ElectronHeader } from \"../modules/layouts/ElectronHeader\";\nimport { ButtonLink } from \"../ui/ButtonLink\";\n\ninterface logoutProps {}\n\n// purpose of this page is to wait for token store to be cleared\n// should be done by the component sending the user here\n// then it should redirect to landing page\nconst Logout: React.FC<logoutProps> = ({}) => {\n  const [hasTokens, setTokens] = useTokenStore((s) => [\n    !!(s.accessToken && s.refreshToken),\n    s.setTokens,\n  ]);\n  const { replace } = useRouter();\n  useEffect(() => {\n    if (!hasTokens) {\n      replace(\"/\");\n    }\n  }, [hasTokens, replace]);\n\n  return (\n    <>\n      <ElectronHeader />\n      <ButtonLink\n        onClick={() => setTokens({ accessToken: \"\", refreshToken: \"\" })}\n      >\n        click here if you are not automatically redirected\n      </ButtonLink>\n    </>\n  );\n};\n\nexport default Logout;\n"
  },
  {
    "path": "kibbeh/src/pages/overlay-settings.tsx",
    "content": "import { OverlaySettingsPage } from \"../modules/settings/OverlaySettingsPage\";\n\nexport default OverlaySettingsPage;\n"
  },
  {
    "path": "kibbeh/src/pages/privacy-settings.tsx",
    "content": "import { PrivacySettingsPage } from \"../modules/settings/PrivacySettingsPage\";\n\nexport default PrivacySettingsPage;\n"
  },
  {
    "path": "kibbeh/src/pages/room/[id]/index.tsx",
    "content": "import { RoomPage } from \"../../../modules/room/RoomPage\";\n\nexport default RoomPage;\n"
  },
  {
    "path": "kibbeh/src/pages/room/[id]/invite.tsx",
    "content": "import { InviteRoomPage } from \"../../../modules/room/InviteRoomPage\";\n\nexport default InviteRoomPage;\n"
  },
  {
    "path": "kibbeh/src/pages/scheduled-room/[id].tsx",
    "content": "import { ViewScheduledRoomPage } from \"../../modules/room/ViewScheduledRoomPage\";\n\nexport default ViewScheduledRoomPage;\n"
  },
  {
    "path": "kibbeh/src/pages/scheduled-rooms.tsx",
    "content": "import React from \"react\";\nimport { WaitForWsAndAuth } from \"../modules/auth/WaitForWsAndAuth\";\nimport { HeaderController } from \"../modules/display/HeaderController\";\nimport { DefaultDesktopLayout } from \"../modules/layouts/DefaultDesktopLayout\";\nimport { ScheduledRoomsPage } from \"../modules/scheduled-rooms/ScheduledRoomsPage\";\nimport { PageComponent } from \"../types/PageComponent\";\nimport { InfoText } from \"../ui/InfoText\";\nexport default ScheduledRoomsPage;\n"
  },
  {
    "path": "kibbeh/src/pages/search.tsx",
    "content": "import { SearchPage } from \"../modules/search/SearchPage\";\n\nexport default SearchPage;\n"
  },
  {
    "path": "kibbeh/src/pages/sound-effect-settings.tsx",
    "content": "import { SoundEffectSettings } from \"../modules/settings/SoundEffectSettingsPage\";\n\nexport default SoundEffectSettings;\n"
  },
  {
    "path": "kibbeh/src/pages/u/[username]/followers.tsx",
    "content": "import { FollowingPage } from \"../../../modules/user/FollowingPage\";\n\nexport default FollowingPage;\n"
  },
  {
    "path": "kibbeh/src/pages/u/[username]/following-online.tsx",
    "content": "import { FollowingOnlinePage } from \"../../../modules/user/FollowingOnlinePage\";\n\nexport default FollowingOnlinePage;\n"
  },
  {
    "path": "kibbeh/src/pages/u/[username]/following.tsx",
    "content": "import { FollowingPage } from \"../../../modules/user/FollowingPage\";\n\nexport default FollowingPage;\n"
  },
  {
    "path": "kibbeh/src/pages/u/[username]/index.tsx",
    "content": "import { UserPage } from \"../../../modules/user/UserPage\";\n\nexport default UserPage;\n"
  },
  {
    "path": "kibbeh/src/pages/voice-settings.tsx",
    "content": "import { VoiceSettingsPage } from \"../modules/settings/VoiceSettingsPage\";\n\nexport default VoiceSettingsPage;\n"
  },
  {
    "path": "kibbeh/src/shared-components/ApiPreloadLink.tsx",
    "content": "import Link from \"next/link\";\nimport { useRouter } from \"next/router\";\nimport React from \"react\";\nimport { useTypeSafePrefetch } from \"../shared-hooks/useTypeSafePrefetch\";\n\ntype Prefetch = ReturnType<typeof useTypeSafePrefetch>;\n\nconst handlers = {\n  following: ({ username }: { username: string }) => ({\n    href: \"/u/[username]/following\",\n    as: `/u/${username}/following`,\n    onClick: (prefetch: Prefetch) =>\n      prefetch(\"getFollowList\", [username, true, 0]),\n  }),\n  followers: ({ username }: { username: string }) => ({\n    href: \"/u/[username]/followers\",\n    as: `/u/${username}/followers`,\n    onClick: (prefetch: Prefetch) =>\n      prefetch(\"getFollowList\", [username, false, 0]),\n  }),\n  profile: ({ username }: { username: string }) => ({\n    href: \"/u/[username]\",\n    as: `/u/${username}`,\n    onClick: (prefetch: Prefetch) => prefetch(\"getUserProfile\", [username]),\n  }),\n  room: ({ id }: { id: string }) => ({\n    href: \"/room/[id]\",\n    as: `/room/${id}`,\n    onClick: (prefetch: Prefetch) => prefetch(\"joinRoomAndGetInfo\", [id]),\n  }),\n};\n\ntype Handler = typeof handlers;\n\ntype ValueOf<T> = T[keyof T];\ntype DifferentProps = {\n  [K in keyof Handler]: {\n    route: K;\n    data: Parameters<Handler[K]>[0];\n  };\n};\n\n// the purpose of this component is to start the query to the api before navigating to the page\n// this will result in less loading time for the user\nexport const ApiPreloadLink: React.FC<ValueOf<DifferentProps>> = ({\n  children,\n  route,\n  data,\n  ...props\n}) => {\n  const prefetch = useTypeSafePrefetch();\n\n  const { as, href, onClick } = handlers[route](data as any);\n\n  return (\n    <Link href={href} as={as}>\n      <a {...props} onClick={() => onClick(prefetch)}>\n        {children}\n      </a>\n    </Link>\n  );\n};\n\nexport const usePreloadPush = () => {\n  const { push } = useRouter();\n  const prefetch = useTypeSafePrefetch();\n  return ({ route, data }: ValueOf<DifferentProps>) => {\n    const { as, href, onClick } = handlers[route](data as any);\n    onClick(prefetch);\n    push(href, as);\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-components/ConfirmModal.tsx",
    "content": "import * as React from \"react\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../ui/Button\";\nimport { ButtonLink } from \"../ui/ButtonLink\";\nimport { Modal } from \"../ui/Modal\";\n\ninterface Props {}\n\ntype Fn = () => void;\n\nconst useConfirmModalStore = create(\n  combine(\n    {\n      message: \"\",\n      onConfirm: undefined as undefined | Fn,\n    },\n    (set) => ({\n      close: () => set({ onConfirm: undefined, message: \"\" }),\n      set,\n    })\n  )\n);\n\nexport const modalConfirm = (message: string, onConfirm: Fn) => {\n  useConfirmModalStore.getState().set({ onConfirm, message });\n};\n\nexport const ConfirmModal: React.FC<Props> = () => {\n  const { onConfirm, message, close } = useConfirmModalStore();\n  const { t } = useTypeSafeTranslation();\n  return (\n    <Modal isOpen={!!onConfirm} onRequestClose={() => close()}>\n      <div className=\"flex flex-col\">\n        <div className={`flex text-primary-100`}>{message}</div>\n        <div className={`flex mt-6 items-center`}>\n          <Button\n            onClick={() => {\n              close();\n              onConfirm?.();\n            }}\n            type=\"submit\"\n          >\n            {t(\"common.yes\")}\n          </Button>\n          <ButtonLink\n            type=\"button\"\n            onClick={close}\n            className={`ml-4`}\n            color=\"secondary\"\n          >\n            {t(\"common.cancel\")}\n          </ButtonLink>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/shared-components/InvitedToJoinRoomModal.tsx",
    "content": "import { Router } from \"next/router\";\nimport * as React from \"react\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../modules/sound-effects/useSoundEffectStore\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"../ui/Button\";\nimport { ButtonLink } from \"../ui/ButtonLink\";\nimport { Modal } from \"../ui/Modal\";\nimport { SingleUser } from \"../ui/UserAvatar\";\n\ninterface Props {}\n\ntype Fn = () => void;\n\nexport type JoinRoomModalType = \"invite\" | \"someone_you_follow_created_a_room\";\n\nexport type UserPreviewInfo = {\n  username: string;\n  displayName: string;\n  avatarUrl: string;\n};\n\ntype Options = {\n  type: JoinRoomModalType;\n  roomId: string;\n  roomName: string;\n  onConfirm: Fn;\n} & UserPreviewInfo;\n\nconst useConfirmModalStore = create(\n  combine(\n    {\n      options: null as null | Options,\n    },\n    (set) => ({\n      close: () => set({ options: null }),\n      set,\n    })\n  )\n);\n\nexport const invitedToRoomConfirm = (\n  options: Omit<Options, \"onConfirm\">,\n  push: Router[\"push\"]\n) => {\n  useSoundEffectStore.getState().playSoundEffect(\"roomInvite\");\n  useConfirmModalStore.getState().set({\n    options: {\n      ...options,\n      onConfirm: () => {\n        push(`/room/[id]`, `/room/${options.roomId}`);\n      },\n    },\n  });\n};\n\nexport const InvitedToJoinRoomModal: React.FC<Props> = () => {\n  const { options, close } = useConfirmModalStore();\n  const { t } = useTypeSafeTranslation();\n  return (\n    <Modal isOpen={!!options} onRequestClose={() => close()}>\n      <div className=\"flex flex-col\">\n        {options ? (\n          <div className=\"flex flex-col text-primary-100\">\n            <h1 className={`text-2xl mb-2`}>\n              {options.type === \"someone_you_follow_created_a_room\"\n                ? t(\"components.modals.invitedToJoinRoomModal.newRoomCreated\")\n                : t(\"components.modals.invitedToJoinRoomModal.roomInviteFrom\")}\n            </h1>\n            <div className={`flex items-center`}>\n              <SingleUser size=\"md\" src={options.avatarUrl} />\n              <div className={`flex ml-2 flex-col`}>\n                <div className={`flex font-bold`}>{options.displayName}</div>\n                <div className={`flex my-1 flex`}>\n                  <div className=\"flex\">@{options.username}</div>\n                </div>\n              </div>\n            </div>\n            <div className={`mt-4`}>\n              {options.type === \"someone_you_follow_created_a_room\"\n                ? t(\"components.modals.invitedToJoinRoomModal.justStarted\")\n                : t(\n                    \"components.modals.invitedToJoinRoomModal.inviteReceived\"\n                  )}{\" \"}\n              <span className={`font-semibold ml-1`}>{options.roomName}</span>\n              {t(\"components.modals.invitedToJoinRoomModal.likeToJoin\")}\n            </div>\n          </div>\n        ) : null}\n        <div className={`flex mt-4 items-center`}>\n          <Button\n            onClick={() => {\n              close();\n              options?.onConfirm();\n            }}\n            type=\"submit\"\n          >\n            {t(\"common.yes\")}\n          </Button>\n          <ButtonLink\n            type=\"button\"\n            onClick={close}\n            className={`ml-4`}\n            color=\"secondary\"\n          >\n            {t(\"common.cancel\")}\n          </ButtonLink>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useBoundingClientRect.ts",
    "content": "import { MutableRefObject, useState, useEffect, useCallback } from \"react\";\n\nfunction getBoundingClientRect(\n  element: HTMLElement\n): ClientRect | DOMRect | null {\n  return element.getBoundingClientRect();\n}\n\nfunction useBoundingClientRect(\n  ref: MutableRefObject<HTMLElement | null>\n): ClientRect | DOMRect | null {\n  const [value, setValue] = useState<ClientRect | DOMRect | null>(null);\n\n  const update = useCallback(() => {\n    setValue(ref.current ? getBoundingClientRect(ref.current) : null);\n  }, [ref]);\n\n  useEffect(() => {\n    update();\n  }, [update]);\n\n  return value;\n}\n\nexport { useBoundingClientRect };\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useConn.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useContext } from \"react\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\n\nexport const useConn = () => {\n  return useContext(WebSocketContext).conn!;\n};\n\nexport const useWrappedConn = () => {\n  return wrap(useContext(WebSocketContext).conn!);\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useCurrentRoomFromCache.ts",
    "content": "import { useCurrentRoomId } from \"./useCurrentRoomId\";\nimport { useTypeSafeQuery } from \"./useTypeSafeQuery\";\n\nexport const useCurrentRoomFromCache = () => {\n  const roomId = useCurrentRoomId();\n  // this should read from the cache\n  const { data } = useTypeSafeQuery(\n    [\"joinRoomAndGetInfo\", roomId!],\n    { enabled: false },\n    [roomId!]\n  );\n\n  return data;\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useCurrentRoomId.ts",
    "content": "import { useRouter } from \"next/router\";\nimport { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\n\nexport const useCurrentRoomId = () => {\n  const { pathname, query } = useRouter();\n  const { currentRoomId } = useCurrentRoomIdStore();\n  if (pathname === \"/room/[id]\" && query.id && typeof query.id === \"string\") {\n    return query.id;\n  }\n  return currentRoomId;\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useCurrentRoomInfo.ts",
    "content": "import isElectron from \"is-electron\";\nimport { useContext } from \"react\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { useCurrentRoomFromCache } from \"./useCurrentRoomFromCache\";\n\nlet roomModData: { [id: string]: boolean } = {};\nlet ipcRenderer: any = undefined;\n\nexport const useCurrentRoomInfo = () => {\n  const data = useCurrentRoomFromCache();\n  const { conn } = useContext(WebSocketContext);\n\n  if (!data || !conn || \"error\" in data) {\n    return {\n      isMod: false,\n      isCreator: false,\n      isSpeaker: false,\n      canSpeak: false,\n    };\n  }\n\n  let isMod = false;\n  let isSpeaker = false;\n  let canIAskToSpeak = false;\n  const me = conn.user;\n  const isCreator = me.id === data.room.creatorId;\n\n  const { users } = data;\n\n  for (const u of users) {\n    if (u.id === me.id) {\n      if (u.roomPermissions?.isSpeaker) {\n        isSpeaker = true;\n      }\n      if (u.roomPermissions?.isMod) {\n        isMod = true;\n      }\n      canIAskToSpeak =\n        !u.roomPermissions?.askedToSpeak && !isCreator && !isSpeaker;\n      break;\n    }\n  }\n\n  if (isElectron()) {\n    const currentRoomId = data.room.id;\n    if (!roomModData) {\n      roomModData = { [currentRoomId]: false };\n    }\n    if (!roomModData[currentRoomId]) {\n      roomModData[currentRoomId] = false;\n    }\n    if (roomModData[currentRoomId] !== isMod) {\n      roomModData[currentRoomId] = isMod;\n      ipcRenderer = window.require(\"electron\").ipcRenderer;\n      ipcRenderer.send(\"@notification/mod\", isMod);\n    }\n  }\n  return {\n    isCreator,\n    isMod,\n    isSpeaker,\n    canIAskToSpeak,\n    canSpeak: isCreator || isSpeaker,\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useDevices.ts",
    "content": "import { useState, useEffect, useCallback } from \"react\";\n\nexport const useDevices = () => {\n  const [devices, setDevices] = useState<Array<{ id: string; label: string }>>(\n    []\n  );\n\n  const fetchMics = useCallback(() => {\n    navigator.mediaDevices?.getUserMedia({ audio: true }).then(() => {\n      navigator.mediaDevices\n        ?.enumerateDevices()\n        .then((d) =>\n          setDevices(\n            d\n              .filter(\n                (device) => device.kind === \"audioinput\" && device.deviceId\n              )\n              .map((device) => ({ id: device.deviceId, label: device.label }))\n          )\n        );\n    });\n  }, []);\n\n  useEffect(() => {\n    fetchMics();\n  }, [fetchMics]);\n\n  return {\n    devices,\n    fetchMics,\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useIntersectionObserver.ts",
    "content": "import { useEffect, useState, RefObject, useRef } from \"react\";\n\nexport const useIntersectionObserver = ({\n  threshold = 0,\n  root = null,\n  rootMargin = \"0%\",\n}) => {\n  const [entry, setEntry] = useState<IntersectionObserverEntry>();\n  const [node, setNode] = useState<HTMLElement | null>(null);\n  const observer = useRef<IntersectionObserver | null>(null);\n\n  useEffect(() => {\n    if (observer.current) {\n      observer?.current?.disconnect();\n    }\n\n    if (node) {\n      if (window.IntersectionObserver) {\n        observer.current = new window.IntersectionObserver(\n          ([newEntry]) => setEntry(newEntry),\n          {\n            root,\n            rootMargin,\n            threshold,\n          }\n        );\n\n        observer.current.observe(node);\n      }\n    }\n\n    return () => {\n      if (observer?.current) {\n        observer?.current?.disconnect();\n      }\n    };\n  }, [threshold, root, rootMargin, node]);\n\n  return { setNode, entry };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useLeaveRoom.ts",
    "content": "import isElectron from \"is-electron\";\nimport { useCallback } from \"react\";\nimport { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\nimport { useRoomChatStore } from \"../modules/room/chat/useRoomChatStore\";\nimport { closeVoiceConnections } from \"../modules/webrtc/WebRtcApp\";\nimport { useTypeSafeMutation } from \"./useTypeSafeMutation\";\n\nexport const useLeaveRoom = () => {\n  const { mutateAsync, isLoading } = useTypeSafeMutation(\"leaveRoom\");\n\n  return {\n    leaveRoom: useCallback(() => {\n      if (isElectron()) {\n        const ipcRenderer = window.require(\"electron\").ipcRenderer;\n        ipcRenderer.send(\"@room/joined\", false);\n      }\n      mutateAsync([]);\n      useCurrentRoomIdStore.getState().setCurrentRoomId(null);\n      useRoomChatStore.getState().reset();\n      closeVoiceConnections(null);\n    }, [mutateAsync]),\n    isLoading,\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useMainWsHandler.tsx",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport { FC, useContext, useEffect } from \"react\";\nimport { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\nimport { useRoomChatMentionStore } from \"../global-stores/useRoomChatMentionStore\";\nimport { showErrorToast } from \"../lib/showErrorToast\";\nimport { useTokenStore } from \"../modules/auth/useTokenStore\";\nimport {\n  RoomChatMessageToken,\n  useRoomChatStore,\n} from \"../modules/room/chat/useRoomChatStore\";\nimport { mergeRoomPermission } from \"../modules/webrtc/utils/mergeRoomPermission\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { invitedToRoomConfirm } from \"../shared-components/InvitedToJoinRoomModal\";\nimport { setMute } from \"./useSetMute\";\nimport { useTypeSafeUpdateQuery } from \"./useTypeSafeUpdateQuery\";\n\nlet ipcRenderer: any = undefined;\nif (isElectron()) {\n  ipcRenderer = window.require(\"electron\").ipcRenderer;\n}\n\nexport const useMainWsHandler = () => {\n  const { push } = useRouter();\n  const { conn } = useContext(WebSocketContext);\n  const updateQuery = useTypeSafeUpdateQuery();\n\n  useEffect(() => {\n    if (!conn) {\n      return;\n    }\n    const unsubs = [\n      conn.addListener<any>(\n        \"new_room_details\",\n        ({ name, description, isPrivate, roomId }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  room: {\n                    ...data.room,\n                    name,\n                    description,\n                    isPrivate,\n                  },\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"error\", (message) => {\n        showErrorToast(message);\n      }),\n      conn.addListener<any>(\"chat_user_unbanned\", ({ userId }) => {\n        useRoomChatStore.getState().unbanUser(userId);\n      }),\n      conn.addListener<any>(\"chat_user_banned\", ({ userId }) => {\n        useRoomChatStore.getState().addBannedUser(userId);\n      }),\n      conn.addListener<any>(\"new_chat_msg\", ({ msg }) => {\n        const { open } = useRoomChatStore.getState();\n        useRoomChatStore.getState().addMessage(msg);\n        const { isRoomChatScrolledToTop } = useRoomChatStore.getState();\n        if (\n          (!open || !document.hasFocus() || isRoomChatScrolledToTop) &&\n          !!msg.tokens.filter(\n            (t: RoomChatMessageToken) =>\n              t.t === \"mention\" &&\n              t.v?.toLowerCase() === conn.user.username.toLowerCase()\n          ).length\n        ) {\n          useRoomChatMentionStore.getState().incrementIAmMentioned();\n          if (isElectron()) {\n            ipcRenderer.send(\"@notification/mention\", msg);\n          }\n        }\n      }),\n      conn.addListener<any>(\"message_deleted\", ({ messageId, deleterId }) => {\n        const { messages, setMessages } = useRoomChatStore.getState();\n        setMessages(\n          messages.map((m) => ({\n            ...m,\n            deleted: m.id === messageId || !!m.deleted,\n            deleterId: m.id === messageId ? deleterId : m.deleterId,\n          }))\n        );\n      }),\n      conn.addListener<any>(\n        \"room_privacy_change\",\n        ({ roomId, isPrivate, name }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  room: {\n                    ...data.room,\n                    name,\n                    isPrivate,\n                  },\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\n        \"room_chat_throttle_change\",\n        ({ roomId, chatThrottle, name }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  room: {\n                    ...data.room,\n                    name,\n                    chatThrottle,\n                  },\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\n        \"room_chat_mode_changed\",\n        ({ roomId, chatMode }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  room: {\n                    ...data.room,\n                    chatMode,\n                  },\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"banned\", () => {\n        showErrorToast(\"you got banned\");\n        conn.close();\n        useTokenStore\n          .getState()\n          .setTokens({ accessToken: \"\", refreshToken: \"\" });\n      }),\n      conn.addListener<any>(\"ban_done\", ({ worked }) => {\n        if (worked) {\n          showErrorToast(\"ban worked\");\n        } else {\n          showErrorToast(\"ban failed\");\n        }\n      }),\n      conn.addListener<any>(\"someone_you_follow_created_a_room\", (value) => {\n        invitedToRoomConfirm(value, push);\n        if (isElectron()) {\n          ipcRenderer.send(\"@notification/indirect_invitation\", value);\n        }\n      }),\n      conn.addListener<any>(\"invitation_to_room\", (value) => {\n        invitedToRoomConfirm(value, push);\n        if (isElectron()) {\n          ipcRenderer.send(\"@notification/invitation\", value);\n        }\n      }),\n      conn.addListener<any>(\n        \"active_speaker_change\",\n        ({ roomId, activeSpeakerMap }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  activeSpeakerMap,\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"room_destroyed\", ({ roomId }) => {\n        useCurrentRoomIdStore\n          .getState()\n          .setCurrentRoomId((id) => (id === roomId ? null : id));\n\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => ({\n          // @todo change to an error code\n          error: \"room gone\",\n        }));\n      }),\n      conn.addListener<any>(\"new_room_creator\", ({ userId, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          !data || \"error\" in data\n            ? data\n            : {\n                ...data,\n                room: {\n                  ...data.room,\n                  creatorId: userId,\n                },\n              }\n        );\n      }),\n      conn.addListener<any>(\n        \"speaker_removed\",\n        ({ userId, roomId, muteMap, deafMap }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  muteMap,\n                  deafMap,\n                  users: data.users.map((x) =>\n                    userId === x.id\n                      ? {\n                          ...x,\n                          roomPermissions: mergeRoomPermission(\n                            x.roomPermissions,\n                            { isSpeaker: false, askedToSpeak: false }\n                          ),\n                        }\n                      : x\n                  ),\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\n        \"speaker_added\",\n        ({ userId, roomId, muteMap, deafMap }) => {\n          // Mute user upon added as speaker\n          if (conn.user.id === userId) {\n            setMute(wrap(conn), true);\n          }\n\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  muteMap,\n                  deafMap,\n                  users: data.users.map((x) =>\n                    userId === x.id\n                      ? {\n                          ...x,\n                          roomPermissions: mergeRoomPermission(\n                            x.roomPermissions,\n                            {\n                              isSpeaker: true,\n                            }\n                          ),\n                        }\n                      : x\n                  ),\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"mod_changed\", ({ userId, roomId, isMod }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          !data || \"error\" in data\n            ? data\n            : {\n                ...data,\n                users: data.users.map((x) =>\n                  userId === x.id\n                    ? {\n                        ...x,\n                        roomPermissions: mergeRoomPermission(\n                          x.roomPermissions,\n                          { isMod }\n                        ),\n                      }\n                    : x\n                ),\n              }\n        );\n      }),\n      conn.addListener<any>(\"user_left_room\", ({ userId, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => {\n          if (data && \"error\" in data) {\n            return data;\n          }\n\n          const { [userId]: _, ...asm } = data.activeSpeakerMap;\n          return {\n            ...data,\n            activeSpeakerMap: asm,\n            room: {\n              ...data.room,\n              peoplePreviewList: data.room.peoplePreviewList.filter(\n                (x) => x.id !== userId\n              ),\n              numPeopleInside: data.room.numPeopleInside - 1,\n            },\n            users: data.users.filter((x) => x.id !== userId),\n          };\n        });\n      }),\n      conn.addListener<any>(\n        \"new_user_join_room\",\n        ({ user, muteMap, deafMap, roomId }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            !data || \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  muteMap,\n                  deafMap,\n                  room: {\n                    ...data.room,\n                    peoplePreviewList:\n                      data.room.peoplePreviewList.length < 10\n                        ? [\n                            ...data.room.peoplePreviewList,\n                            {\n                              id: user.id,\n                              displayName: user.displayName,\n                              numFollowers: user.numFollowers,\n                              avatarUrl: user.avatarUrl,\n                            },\n                          ]\n                        : data.room.peoplePreviewList,\n                    numPeopleInside: data.room.numPeopleInside + 1,\n                  },\n                  users: [...data.users.filter((x) => x.id !== user.id), user],\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"hand_raised\", ({ roomId, userId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          !data || \"error\" in data\n            ? data\n            : {\n                ...data,\n                users: data.users.map((u) =>\n                  u.id === userId\n                    ? {\n                        ...u,\n                        roomPermissions: mergeRoomPermission(\n                          u.roomPermissions,\n                          {\n                            askedToSpeak: true,\n                          }\n                        ),\n                      }\n                    : u\n                ),\n              }\n        );\n      }),\n      conn.addListener<any>(\"mute_changed\", ({ userId, value, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => {\n          if (data && \"error\" in data) {\n            return data;\n          }\n          let muteMap = data.muteMap;\n          if (value) {\n            muteMap = { ...data.muteMap, [userId]: true };\n          } else {\n            const { [userId]: _, ...newMm } = data.muteMap;\n            muteMap = newMm;\n          }\n          return {\n            ...data,\n            muteMap,\n          };\n        });\n      }),\n      conn.addListener<any>(\"deafen_changed\", ({ userId, value, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => {\n          if (data && \"error\" in data) {\n            return data;\n          }\n          let deafMap = data.deafMap;\n          if (value) {\n            deafMap = { ...data.deafMap, [userId]: true };\n          } else {\n            const { [userId]: _, ...newDm } = data.deafMap;\n            deafMap = newDm;\n          }\n          return {\n            ...data,\n            deafMap,\n          };\n        });\n      }),\n    ];\n\n    return () => {\n      unsubs.forEach((u) => u());\n    };\n  }, [conn, updateQuery, push]);\n};\n\nexport const MainWsHandlerProvider: FC = ({ children }) => {\n  useMainWsHandler();\n  return <>{children}</>;\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useOnClickOutside.tsx",
    "content": "import { useEffect, RefObject } from \"react\";\n\nexport const useOnClickOutside = (\n  ref: RefObject<any>,\n  handler: (e: MouseEvent | TouchEvent) => void\n) => {\n  useEffect(() => {\n    const listener = (event: MouseEvent | TouchEvent) => {\n      if (!ref.current || ref.current.contains(event.target)) {\n        return;\n      }\n      handler(event);\n    };\n    document.addEventListener(\"mousedown\", listener);\n    document.addEventListener(\"touchstart\", listener);\n    return () => {\n      document.removeEventListener(\"mousedown\", listener);\n      document.removeEventListener(\"touchstart\", listener);\n    };\n  }, [ref, handler]);\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/usePageVisibility.ts",
    "content": "import { useEffect, useState } from \"react\";\n\nconst usePageVisibility = () => {\n  const [visible, setVisible] = useState<boolean>(true);\n\n  useEffect(() => {\n    const handleVisibility = () => {\n      const isVisible = document.visibilityState === \"visible\" || !document.hidden;\n      setVisible(isVisible);\n    };\n    document.addEventListener(\"visibilitychange\", handleVisibility);\n    return () => {\n      document.removeEventListener(\"visibilitychange\", handleVisibility);\n    };\n  }, []);\n\n  return visible;\n};\n\nexport default usePageVisibility;\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useScreenType.ts",
    "content": "import { useMediaQuery } from \"react-responsive\";\n\nexport const useScreenType = () => {\n  const is3Cols = useMediaQuery({ minWidth: 1336 });\n  const is2Cols = useMediaQuery({ minWidth: 1265 });\n  const is1Cols = useMediaQuery({ minWidth: 800 });\n\n  if (is3Cols) {\n    return \"3-cols\";\n  }\n  if (is2Cols) {\n    return \"2-cols\";\n  }\n  if (is1Cols) {\n    return \"1-cols\";\n  }\n\n  return \"fullscreen\";\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useScreenWakeLockStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport \"wakelock-lazy-polyfill\";\n\nexport const useWakeLockStore = create(\n  combine(\n    {\n      wakeLock: null as WakeLockSentinel | null,\n    },\n    (set, get) => ({\n      requestWakeLock: async () => {\n        try {\n          const wakeLock: WakeLockSentinel | null = await navigator.wakeLock.request(\n            \"screen\"\n          );\n          set({ wakeLock });\n        } catch (error) {\n          console.log(\"wake lock request failed\", { error });\n        }\n      },\n      releaseWakeLock: async () => {\n        const { wakeLock } = get();\n        if (!wakeLock) return;\n        try {\n          await wakeLock.release();\n          set({ wakeLock: null });\n        } catch (error) {\n          console.log(\"Wake lock releasing failed\", { error });\n        }\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useSetDeaf.ts",
    "content": "import { Wrapper } from \"@dogehouse/kebab\";\nimport { useDeafStore } from \"../global-stores/useDeafStore\";\nimport { useMuteStore } from \"../global-stores/useMuteStore\";\nimport { useWrappedConn } from \"./useConn\";\n\nexport const setDeaf = (conn: Wrapper, value: boolean) => {\n  const { muted, setInternalMute } = useMuteStore.getState();\n  if (muted) {\n    setInternalMute(false, false);\n    conn.mutation.setMute(false);\n  }\n  useDeafStore.getState().setInternalDeaf(value);\n  conn.mutation.setDeaf(value);\n};\n\nexport const useSetDeaf = () => {\n  const conn = useWrappedConn();\n  return (value: boolean) => {\n    setDeaf(conn, value);\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useSetMute.ts",
    "content": "import { Wrapper } from \"@dogehouse/kebab\";\nimport { useDeafStore } from \"../global-stores/useDeafStore\";\nimport { useMuteStore } from \"../global-stores/useMuteStore\";\nimport { useWrappedConn } from \"./useConn\";\n\nexport const setMute = (conn: Wrapper, value: boolean) => {\n  const { setInternalDeaf, deafened } = useDeafStore.getState();\n  // auto undeafen on unmute\n  if (deafened) {\n    setInternalDeaf(false);\n    conn.mutation.setDeaf(false);\n  } else {\n    useMuteStore.getState().setInternalMute(value);\n    conn.mutation.setMute(value);\n  }\n};\n\nexport const useSetMute = () => {\n  const conn = useWrappedConn();\n  return (value: boolean) => {\n    setMute(conn, value);\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useTypeSafeMutation.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useContext } from \"react\";\nimport { useMutation, UseMutationOptions } from \"react-query\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { Await } from \"../types/util-types\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"mutation\"];\n\nexport const useTypeSafeMutation = <K extends Keys>(\n  key: K,\n  opts?: UseMutationOptions<\n    Await<ReturnType<ReturnType<typeof wrap>[\"mutation\"][K]>>,\n    any,\n    Parameters<ReturnType<typeof wrap>[\"mutation\"][K]>,\n    any\n  >\n) => {\n  const { conn } = useContext(WebSocketContext);\n\n  return useMutation<\n    Await<ReturnType<ReturnType<typeof wrap>[\"mutation\"][K]>>,\n    any,\n    Parameters<ReturnType<typeof wrap>[\"mutation\"][K]>\n  >(\n    (params) =>\n      (wrap(conn!).mutation[typeof key === \"string\" ? key : key[0]] as any)(\n        ...params\n      ),\n    opts\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useTypeSafePrefetch.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useCallback, useContext } from \"react\";\nimport { useQueryClient } from \"react-query\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"query\"];\n\ntype PaginatedKey<K extends Keys> = [K, string | number];\n\nexport const useTypeSafePrefetch = () => {\n  const { conn } = useContext(WebSocketContext);\n  const client = useQueryClient();\n\n  return useCallback(\n    <K extends Keys>(\n      key: K | PaginatedKey<K>,\n      params?: Parameters<ReturnType<typeof wrap>[\"query\"][K]>\n    ) =>\n      client.prefetchQuery(\n        key,\n        () =>\n          (wrap(conn!).query[typeof key === \"string\" ? key : key[0]] as any)(\n            ...(params || [])\n          ),\n        { staleTime: 0 }\n      ),\n    [conn, client]\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useTypeSafeQuery.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useContext } from \"react\";\nimport { useQuery, UseQueryOptions } from \"react-query\";\nimport { isServer } from \"../lib/isServer\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { Await } from \"../types/util-types\";\nimport { useWrappedConn } from \"./useConn\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"query\"];\n\ntype PaginatedKey<K extends Keys> = [K, ...(string | number | boolean)[]];\n\nexport const useTypeSafeQuery = <K extends Keys>(\n  key: K | PaginatedKey<K>,\n  opts?: UseQueryOptions,\n  params?: Parameters<ReturnType<typeof wrap>[\"query\"][K]>\n) => {\n  const conn = useWrappedConn();\n\n  return useQuery<Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>>(\n    key,\n    () => {\n      const fn = conn.query[typeof key === \"string\" ? key : key[0]] as any;\n      return fn(...(params || []));\n    },\n    {\n      enabled: !!conn && !isServer,\n      ...opts,\n    } as any\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useTypeSafeTranslation.ts",
    "content": "import { useTranslation } from \"react-i18next\";\n\nimport { Paths } from \"../types/util-types\";\nimport translations from \"../../public/locales/en/translation.json\";\n\ntype TranslationKeys = Paths<typeof translations>;\n\ninterface DateTranslationType {\n  time?: Date;\n  date?: Date;\n}\n\nexport const useTypeSafeTranslation = () => {\n  const { t } = useTranslation();\n\n  return {\n    t: (s: TranslationKeys, f?: DateTranslationType) => t(s, f),\n  };\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useTypeSafeUpdateQuery.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useCallback, useContext } from \"react\";\nimport { useQuery, useQueryClient, UseQueryOptions } from \"react-query\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { Await } from \"../types/util-types\";\nimport { useWrappedConn } from \"./useConn\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"query\"];\n\ntype PaginatedKey<K extends Keys> = [K, ...(string | number | boolean)[]];\n\nexport const useTypeSafeUpdateQuery = () => {\n  const client = useQueryClient();\n  return useCallback(\n    <K extends Keys>(\n      key: K | PaginatedKey<K>,\n      fn: (\n        x: Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>\n      ) => Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>\n    ) => {\n      client.setQueryData<\n        Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>\n      >(key, fn as any);\n    },\n    [client]\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useViewportSize.ts",
    "content": "import { debounce } from \"lodash\";\nimport { useEffect, useState } from \"react\";\n\ninterface WindowSize {\n  width: number;\n  height: number;\n}\n\nconst useWindowSize = () => {\n  const [windowSize, setWindowSize] = useState<WindowSize>({\n    width: window.visualViewport?.width ?? window.innerWidth,\n    height: window.visualViewport?.height ?? window.innerHeight,\n  });\n\n  useEffect(() => {\n    const handleResize = () => {\n      setWindowSize({\n        width: window.visualViewport?.width ?? window.innerWidth,\n        height: window.visualViewport?.height ?? window.innerHeight,\n      });\n    };\n\n    const debounced = debounce(handleResize, 1000);\n\n    if(!window.visualViewport) {\n      window.addEventListener(\"resize\", debounced);\n    } else {\n      window.visualViewport.addEventListener(\"resize\", debounced);\n    }\n\n    handleResize();\n\n    return () => {\n      debounced.cancel(); // Prevents killing func while handleResize is running\n      if(!window.visualViewport) {\n        window.removeEventListener(\"resize\", debounced);\n      } else {\n        window.visualViewport.removeEventListener(\"resize\", debounced);\n      }\n    };\n  }, []);\n\n  return windowSize;\n};\n\nexport default useWindowSize;\n"
  },
  {
    "path": "kibbeh/src/shared-hooks/useWindowSize.ts",
    "content": "import { debounce } from \"lodash\";\nimport { useEffect, useState } from \"react\";\n\ninterface WindowSize {\n  width: number;\n  height: number;\n}\n\nconst useWindowSize = () => {\n  const [windowSize, setWindowSize] = useState<WindowSize>({\n    width: window.innerWidth,\n    height: window.innerHeight,\n  });\n\n  useEffect(() => {\n    const handleResize = () => {\n      setWindowSize({\n        width: window.innerWidth,\n        height: window.innerHeight,\n      });\n    };\n\n    const debounced = debounce(handleResize, 1000);\n\n    window.addEventListener(\"resize\", debounced);\n\n    handleResize();\n\n    return () => {\n      debounced.cancel(); // Prevents killing func while handleResize is running\n      window.removeEventListener(\"resize\", debounced);\n    };\n  }, []);\n\n  return windowSize;\n};\n\nexport default useWindowSize;\n"
  },
  {
    "path": "kibbeh/src/stories/AccountOverlay.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  AccountOverlay,\n  AccountOverlyProps,\n} from \"../ui/mobile/AccountOverlay\";\n\nexport default {\n  title: \"AccountOverlay\",\n  component: AccountOverlay,\n};\n\nexport const Main: Story<AccountOverlyProps> = (props) => {\n  return (\n    <div className=\"bg-primary-100 w-screen h-screen\">\n      <AccountOverlay></AccountOverlay>\n    </div>\n  );\n};\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/BaseDropdownSm.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { BaseDropdownSm, BaseDropdownSmItem } from \"../ui/BaseDropdownSm\";\n\nexport default {\n  title: \"BaseDropdownSm\",\n};\n\nconst BaseDropdownSmStory: Story = () => {\n  return (\n    <BaseDropdownSm>\n      <BaseDropdownSmItem>Apple Calendar</BaseDropdownSmItem>\n      <BaseDropdownSmItem>Google</BaseDropdownSmItem>\n      <BaseDropdownSmItem>Outlook</BaseDropdownSmItem>\n      <BaseDropdownSmItem>Outlook Web App</BaseDropdownSmItem>\n      <BaseDropdownSmItem>Yahoo</BaseDropdownSmItem>\n    </BaseDropdownSm>\n  );\n};\n\nexport const Main = BaseDropdownSmStory;\n"
  },
  {
    "path": "kibbeh/src/stories/BaseOverlay.story.tsx",
    "content": "import React, { ReactNode } from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { BaseOverlay, BaseOverlayProps } from \"../ui/BaseOverlay\";\nimport { MessageElement } from \"../ui/MessageElement\";\nimport avatar from \"../img/avatar.png\";\nimport { SettingsIcon } from \"../ui/SettingsIcon\";\nimport { SolidCaretRight, OutlineGlobe, SolidUser } from \"../icons\";\nimport {\n  GenericNotification,\n  FollowNotification,\n  LiveNotification,\n  NewRoomNotification,\n} from \"../ui/NotificationElement\";\n\nconst user = {\n  avatar,\n  username: \"TerryOwen\",\n  isOnline: true,\n};\n\nconst msg = {\n  text:\n    \"Hey! I really liked your room, but would like to contribute to dogehouse\",\n  ts: 1615116474,\n};\n\nexport default {\n  title: \"BaseOverlay\",\n  component: BaseOverlay,\n};\n\nexport const Messages: Story<BaseOverlayProps> = ({\n  title = \"Messages\",\n  actionButton: actionLabel = \"Show More\",\n}) => (\n  <div className=\"flex\" style={{ width: 444 }}>\n    <BaseOverlay title={title} actionButton={actionLabel}>\n      <MessageElement user={user} msg={msg} />\n      <MessageElement user={user} msg={msg} />\n      <MessageElement user={user} msg={msg} />\n    </BaseOverlay>\n  </div>\n);\n\nMessages.bind({});\n\ninterface IconWrapperProps {\n  children: ReactNode;\n}\n\nfunction IconWrapper({ children }: IconWrapperProps) {\n  return <div className=\"flex py-3 px-4\">{children}</div>;\n}\n\nexport const Notifications: Story<BaseOverlayProps> = ({\n  title = \"Notifications\",\n}) => (\n  <div className=\"flex\" style={{ width: 444 }}>\n    <BaseOverlay title={title}>\n      <IconWrapper>\n        <GenericNotification\n          notificationMsg=\"you have a new notification\"\n          time=\"some time ago\"\n        />\n      </IconWrapper>\n      <IconWrapper>\n        <LiveNotification username=\"johndoe\" time=\"some time ago\" />\n      </IconWrapper>\n      <IconWrapper>\n        <NewRoomNotification username={\"johndoe\"} time=\"some time ago\" />\n      </IconWrapper>\n      <IconWrapper>\n        <FollowNotification\n          userAvatarSrc={avatar}\n          username=\"johndoe\"\n          time=\"some time ago\"\n        />\n      </IconWrapper>\n    </BaseOverlay>\n  </div>\n);\n\nNotifications.bind({});\n\nexport const Settings: Story<BaseOverlayProps> = ({\n  actionButton: actionLabel = \"Log out\",\n}) => (\n  <div className=\"flex\" style={{ width: 200 }}>\n    <BaseOverlay actionButton={actionLabel}>\n      <div className=\"flex flex-col\">\n        <SettingsIcon\n          icon={<SolidUser className={`text-primary-100`} />}\n          label={\"profile\"}\n        />\n        <SettingsIcon\n          icon={<OutlineGlobe />}\n          label={\"Language\"}\n          trailingIcon={<SolidCaretRight />}\n        />\n      </div>\n    </BaseOverlay>\n  </div>\n);\n\nSettings.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/BaseSettingsItem.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  BaseSettingsItem,\n  BaseSettingsItemProps,\n} from \"../ui/BaseSettingsItem\";\n\nexport default {\n  title: \"BaseSettingsItem\",\n  component: BaseSettingsItem,\n};\n\nBaseSettingsItem.defaultProps = {\n  children: <></>,\n};\n\nexport const Main: Story<BaseSettingsItemProps> = ({ ...props }) => (\n  <BaseSettingsItem {...props} />\n);\n"
  },
  {
    "path": "kibbeh/src/stories/BoxedIcon.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { BoxedIcon } from \"../ui/BoxedIcon\";\nimport { SolidMicrophone, SolidVolume, SolidFullscreen } from \"../icons\";\n\nexport default {\n  title: \"Boxed Icon\",\n};\n\nconst TheBoxedIcon: Story = () => {\n  return (\n    <div className=\"flex flex-row\">\n      <div className=\"flex m-1\">\n        <BoxedIcon>\n          <SolidMicrophone />\n        </BoxedIcon>\n      </div>\n      <div className=\"flex m-1\">\n        <BoxedIcon>\n          <SolidVolume />\n        </BoxedIcon>\n      </div>\n      <div className=\"flex m-1\">\n        <BoxedIcon>\n          <SolidFullscreen />\n        </BoxedIcon>\n      </div>\n    </div>\n  );\n};\n\nexport const Main = TheBoxedIcon.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/BubbleText.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { BubbleText, BubbleTextProps } from \"../ui/BubbleText\";\nimport { toBoolean } from \"./utils/toBoolean\";\n\nexport default {\n  title: \"BubbleText\",\n};\n\nconst TheBubbleText: Story<BubbleTextProps> = ({ live = false, children }) => {\n  return (\n    <BubbleText live={live}>\n      {children || live ? Math.round(Math.random() * 1000) : \"12:30 PM\"}\n    </BubbleText>\n  );\n};\n\nexport const Main = TheBubbleText.bind({});\n\nMain.argTypes = {\n  live: toBoolean(),\n};\n"
  },
  {
    "path": "kibbeh/src/stories/Button.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { Button, ButtonProps } from \"../ui/Button\";\nimport { toEnum } from \"./utils/toEnum\";\nimport { toBoolean } from \"./utils/toBoolean\";\nimport { SolidDogenitro } from \"../icons\";\n\nexport default {\n  title: \"Button\",\n  argTypes: { onClick: { action: \"clicked\" } },\n};\n\nconst TheButton: Story<ButtonProps & { exampleIcon?: boolean }> = ({\n  children,\n  exampleIcon,\n  ...props\n}) => {\n  return (\n    <Button {...props} icon={exampleIcon ? <SolidDogenitro /> : undefined}>\n      {children || `New room`}\n    </Button>\n  );\n};\n\nexport const Main = TheButton.bind({});\n\nMain.argTypes = {\n  color: toEnum([\"primary\", \"secondary\"]),\n  size: toEnum([\"big\", \"small\"]),\n  disabled: toBoolean(),\n  loading: toBoolean(),\n  exampleIcon: toBoolean(),\n};\n"
  },
  {
    "path": "kibbeh/src/stories/ChangeAvatarCard.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport {\n  ChangeAvatarCard,\n  ChangeAvatarCardProps,\n} from \"../ui/ChangeAvatarCard\";\nimport avatar from \"../img/avatar.png\";\n\nexport default {\n  title: \"ChangeAvatarCard\",\n  component: ChangeAvatarCard,\n};\n\nChangeAvatarCard.defaultProps = {\n  avatarUrl: avatar,\n};\n\nexport const Main: Story<ChangeAvatarCardProps> = ({ ...props }) => (\n  <div className=\"w-3/5 mx-auto\">\n    <ChangeAvatarCard {...props} />\n  </div>\n);\n"
  },
  {
    "path": "kibbeh/src/stories/ChangeBannerCard.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport profileCover from \"./img/profile-cover.png\";\n\nimport {\n  ChangeBannerCard,\n  ChangeBannerCardProps,\n} from \"../ui/ChangeBannerCard\";\n\nexport default {\n  title: \"ChangeBannerCard\",\n  component: ChangeBannerCard,\n};\n\nChangeBannerCard.defaultProps = {\n  bannerUrl: profileCover,\n};\n\nexport const Main: Story<ChangeBannerCardProps> = ({ ...props }) => (\n  <div style={{ width: \"600px\" }}>\n    <ChangeBannerCard {...props} />\n  </div>\n);\n"
  },
  {
    "path": "kibbeh/src/stories/ErrorButtonItem.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { ErrorButtonItem, ErrorButtonItemProps } from \"../ui/ErrorButtonItem\";\n\nexport default {\n  title: \"ErrorButtonItem\",\n  component: ErrorButtonItem,\n};\n\nexport const Main: Story<ErrorButtonItemProps> = ({\n  actionButtonText = \"Refresh\",\n  errorMessageHeading = \"No microphone found\",\n  errorMessageText = \"You either have no device plugged in or haven’t given this website permission\",\n}) => {\n  return (\n    <div className=\"mx-auto\" style={{ width: 600 }}>\n      <ErrorButtonItem\n        actionButtonText={actionButtonText}\n        errorMessageHeading={errorMessageHeading}\n        errorMessageText={errorMessageText}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/stories/ErrorToast.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { ErrorToast, ErrorMessageProps } from \"../ui/Toast\";\nimport { toStr } from \"./utils/toStr\";\nimport { toBoolean } from \"./utils/toBoolean\";\n\nexport default {\n  title: \"ErrorMessage\",\n};\n\nconst TheErrorMessage: Story<ErrorMessageProps> = ({ message, button }) => {\n  return <ErrorToast message={message} button={button} onClose={() => {}} />;\n};\n\nexport const Main = TheErrorMessage.bind({});\n\nMain.args = {\n  message: \"Websocket got disconnected\",\n  button: \"Refresh\",\n};\n\nMain.argTypes = {\n  message: toStr(),\n  button: toStr(),\n  autoClose: toBoolean(),\n};\n"
  },
  {
    "path": "kibbeh/src/stories/FriendsOnline.story.tsx",
    "content": "// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck @todo this file needs to be fixed\n\nimport React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { FollowersOnline, FriendsOnlineProps } from \"../ui/FollowersOnline\";\nimport avatar from \"../img/avatar.png\";\n\nexport default {\n  title: \"FriendsOnline\",\n  component: FollowersOnline,\n};\n\nconst user = {\n  username: \"Lauren Miller\",\n  avatar,\n  isOnline: true,\n  activeRoom: {\n    name: \"The Developers hangout\",\n  },\n};\n\nconst onlineFriendList = [user, user, user, user, user];\n\nexport const Main: Story<FriendsOnlineProps> = ({ ...props }) => (\n  <FollowersOnline\n    {...props}\n    onlineFriendCount={23}\n    onlineFriendList={onlineFriendList}\n  />\n);\n\nMain.bind({});\n\nexport const ZeroFriends: Story<FriendsOnlineProps> = ({ ...props }) => (\n  <FollowersOnline {...props} />\n);\n\nZeroFriends.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/KeybindCard.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { KeybindCard, KeybindCardProps } from \"../ui/KeybindCard\";\n\nexport default {\n  title: \"KeybindCard\",\n  component: KeybindCard,\n};\n\nexport const ToggleMuteKeybind: Story<KeybindCardProps> = ({\n  command = \"toggle mute\",\n  modifier = \"ctrl\",\n  primaryKey = \"m\",\n}) => {\n  return (\n    <div className=\"w-1/2 mx-auto\">\n      <KeybindCard\n        command={command}\n        modifier={modifier}\n        primaryKey={primaryKey}\n      />\n    </div>\n  );\n};\n\nToggleMuteKeybind.bind({});\n\nexport const PushToTalkKeybind: Story<KeybindCardProps> = ({\n  command = \"push-to-talk\",\n  modifier = \"ctrl\",\n  primaryKey = \"1\",\n}) => {\n  return (\n    <div className=\"w-1/2 mx-auto\">\n      <KeybindCard\n        command={command}\n        modifier={modifier}\n        primaryKey={primaryKey}\n      />\n    </div>\n  );\n};\n\nPushToTalkKeybind.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/LeftHeader.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport LeftHeader, { LeftHeaderProps } from \"../ui/header/LeftHeader\";\n\nexport default {\n  title: \"LeftHeader\",\n  component: LeftHeader,\n};\n\nexport const Main: Story<LeftHeaderProps> = () => <LeftHeader />;\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/MessageElement.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { MessageElement, MessageElementProps } from \"../ui/MessageElement\";\n\nexport default {\n  title: \"MessageElement\",\n  component: MessageElement,\n};\n\nimport avatar from \"../img/avatar.png\";\n\nconst user = {\n  avatar,\n  username: \"TerryOwen\",\n  isOnline: true,\n};\n\nconst msg = {\n  text:\n    \"Hey! I really liked your room, but would like to contribute to dogehouse\",\n  ts: 1615116474,\n};\n\nexport const Main: Story<MessageElementProps> = ({ ...props }) => {\n  return (\n    <MessageElement\n      {...props}\n      user={props.user || user}\n      msg={props.msg || msg}\n    />\n  );\n};\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/MessagesDropdown.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport {\n  MessagesDropdown,\n  MessagesDropdownProps,\n} from \"../ui/MessagesDropdown\";\nimport avatar from \"../img/avatar.png\";\n\nexport default {\n  title: \"MessagesDropdown\",\n  component: MessagesDropdown,\n};\n\nexport const ZeroMessages: Story<MessagesDropdownProps> = ({ ...props }) => {\n  return <MessagesDropdown {...props} />;\n};\n\nZeroMessages.bind({});\n\nconst user = {\n  avatar,\n  username: \"TerryOwen\",\n  isOnline: true,\n};\n\nconst msg = {\n  text:\n    \"Hey! I really liked your room, but would like to contribute to dogehouse\",\n  ts: 1615116474,\n};\n\nconst messageList = [\n  { user, msg },\n  { user, msg },\n  { user, msg },\n  { user, msg },\n];\n\nexport const NewMessages: Story<MessagesDropdownProps> = () => {\n  return <MessagesDropdown messageList={messageList} />;\n};\n\nNewMessages.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/MiddleHeader.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { MiddleHeaderProps, MiddleHeader } from \"../ui/header/MiddleHeader\";\n\nexport default {\n  title: \"MiddleHeader\",\n  component: MiddleHeader,\n};\n\nexport const Main: Story<MiddleHeaderProps> = ({ ...props }) => (\n  <MiddleHeader {...props} />\n);\n"
  },
  {
    "path": "kibbeh/src/stories/MinimizedRoomCard.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { MinimizedRoomCard } from \"../ui/MinimizedRoomCard\";\nimport { SearchOverlayProps } from \"../ui/Search/SearchOverlay\";\n\nexport default {\n  title: \"MinimizedRoomCard\",\n  component: MinimizedRoomCard,\n};\n\nconst room = {\n  name:\n    \"Senior Dev / Manager @ GoDaddy (TS/React/GQL) - Ask me whatever you want\",\n  speakers: [\"Terry Owen\", \"Grace Abraham\"],\n  url: \"/room/1829324\",\n  roomStartedAt: new Date(2020, 3, 3),\n  myself: {\n    isSpeaker: true,\n    isMuted: false,\n    isdeafened: false,\n    switchMuted: () => {\n      // no-op\n    },\n    isDeafened: false,\n    switchDeafened: () => {\n      // no-op\n    },\n    leave: () => {\n      // no-op\n    },\n  },\n};\n\nexport const Main: Story<SearchOverlayProps> = ({ ...props }) => (\n  <MinimizedRoomCard {...props} room={room} />\n);\n"
  },
  {
    "path": "kibbeh/src/stories/MobileHeader/PageHeader.story.tsx",
    "content": "import React from \"react\";\n\nimport { Story } from \"@storybook/react\";\nimport {\n  PageHeader,\n  PageHeaderProps,\n} from \"../../ui/mobile/MobileHeader/PageHeader\";\n\nexport default {\n  title: \"MobileHeader/PageHeader\",\n  component: PageHeader,\n};\n\nconst ThePageHeader: Story<PageHeaderProps> = ({\n  title = \"Loren ipsum\",\n  onBackClick = () => null,\n}) => (\n  <div className=\"flex\" style={{ width: 420 }}>\n    <PageHeader title={title} onBackClick={onBackClick} />\n  </div>\n);\n\nexport const Main = ThePageHeader.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/MobileHeader/ProfileHeader.story.tsx",
    "content": "import React from \"react\";\n\nimport { Story } from \"@storybook/react\";\nimport {\n  ProfileHeader,\n  ProfileHeaderProps,\n} from \"../../ui/mobile/MobileHeader/ProfileHeader\";\n\nimport src from \"../../img/avatar.png\";\n\nexport default {\n  title: \"MobileHeader/ProfileHeader\",\n  component: ProfileHeader,\n};\n\nconst TheProfileHeader: Story<ProfileHeaderProps> = ({\n  avatar = src,\n  onAnnouncementsClick = () => null,\n  onMessagesClick = () => null,\n  onSearchClick = () => null,\n}) => (\n  <div className=\"flex\" style={{ width: 420 }}>\n    <ProfileHeader\n      avatar={avatar}\n      onAnnouncementsClick={onAnnouncementsClick}\n      onMessagesClick={onMessagesClick}\n      onSearchClick={onSearchClick}\n    />\n  </div>\n);\n\nexport const Main = TheProfileHeader.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/MobileHeader/SearchHeader.story.tsx",
    "content": "import React from \"react\";\n\nimport { Story } from \"@storybook/react\";\nimport {\n  SearchHeader,\n  SearchHeaderProps,\n} from \"../../ui/mobile/MobileHeader/SearchHeader\";\n\nexport default {\n  title: \"MobileHeader/SearchHeader\",\n  component: SearchHeader,\n};\n\nconst TheSearchHeader: Story<SearchHeaderProps> = ({\n  onBackClick = () => null,\n  onSearchChange = () => null,\n  searchPlaceholder = \"Search\",\n  searchLoading = false,\n}) => (\n  <div className=\"flex\" style={{ width: 420 }}>\n    <SearchHeader\n      onBackClick={onBackClick}\n      onSearchChange={onSearchChange}\n      searchPlaceholder={searchPlaceholder}\n      searchLoading={searchLoading}\n    />\n  </div>\n);\n\nexport const Main = TheSearchHeader.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/MobileNav.story.tsx",
    "content": "import * as React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { MobileNav, MobileNavProps } from \"../ui/mobile/MobileNav\";\nimport {\n  SolidHome,\n  SolidFriends,\n  SolidCompass,\n  SolidCalendar,\n  SolidNew,\n} from \"../icons\";\n\nexport default {\n  title: \"MobileNav\",\n  component: MobileNav,\n};\n\nconst items = [\n  {\n    targetPath: \"/home\",\n    icon: <SolidHome />,\n  },\n  {\n    targetPath: \"/home\",\n    icon: <SolidCalendar />,\n  },\n  {\n    targetPath: \"/home\",\n    icon: <SolidNew />,\n  },\n  {\n    targetPath: \"/home\",\n    icon: <SolidCompass />,\n  },\n  {\n    targetPath: \"/home\",\n    icon: <SolidFriends />,\n  },\n];\n\nexport const Main: Story<MobileNavProps> = ({ ...props }) => {\n  return <MobileNav {...props} items={items} />;\n};\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/NativeCheckbox.story.tsx",
    "content": "import React from \"react\";\nimport { Meta, Story } from \"@storybook/react\";\nimport {\n  NativeCheckbox,\n  NativeCheckboxController,\n  NativeCheckboxControllerProps,\n  NativeCheckboxProps,\n} from \"../ui/NativeCheckbox\";\n\nexport default {\n  title: \"NativeCheckbox\",\n  component: NativeCheckboxController,\n} as Meta;\n\nexport const Main: Story<NativeCheckboxControllerProps> = ({ checkboxes }) => {\n  return (\n    <div style={{ width: 640 }} className=\"p-4 bg-primary-800\">\n      <NativeCheckboxController checkboxes={checkboxes} />\n    </div>\n  );\n};\n\nMain.bind({});\n\nMain.args = {\n  checkboxes: [\n    {\n      title: \"Whispers\",\n      subtitle: \"Allow room users to whisper you\",\n    },\n    {\n      title: \"Mentions\",\n      subtitle: \"Allow room users to mention you\",\n    },\n    {\n      title: \"Bot Messages\",\n      subtitle: \"Show messages by Bots\",\n    },\n  ],\n};\n"
  },
  {
    "path": "kibbeh/src/stories/NativeRadio.story.tsx",
    "content": "import React from \"react\";\nimport { Meta, Story } from \"@storybook/react\";\nimport {\n  NativeRadio,\n  NativeRadioController,\n  NativeRadioControllerProps,\n} from \"../ui/NativeRadio\";\nimport { SolidMoon } from \"../icons\";\n\nconst meta: Meta<NativeRadioControllerProps> = {\n  title: \"NativeRadio\",\n};\n\nexport default meta;\n\nexport const WithIcon: Story<NativeRadioControllerProps> = (args) => {\n  return (\n    <div style={{ width: 640 }} className=\"bg-primary-800 p-4\">\n      <NativeRadioController radios={args.radios} />\n    </div>\n  );\n};\n\nWithIcon.bind({});\n\nconst Circle: React.FC<{ color: string }> = ({ color }) => (\n  <div className={`rounded-full bg-${color} w-2 h-2`}></div>\n);\n\nWithIcon.args = {\n  radios: [\n    {\n      title: \"Online\",\n      subtitle: \"When you’re online, it will be visible to everyone\",\n      icon: <Circle color=\"accent\" />,\n    },\n    {\n      title: \"Do not disturb\",\n      subtitle: \"Let your followers know that you’re away\",\n      icon: <SolidMoon width=\"10\" height=\"10\" style={{ color: \"#FFA928\" }} />,\n    },\n    {\n      title: \"Offline\",\n      subtitle: \"Wear Harry Potter’s invisibility cloak\",\n      icon: <Circle color=\"primary-300\" />,\n    },\n  ],\n};\n\nexport const WithoutIcon: Story<NativeRadioControllerProps> = (args) => {\n  return (\n    <div style={{ width: 640 }} className=\"bg-primary-800 p-4\">\n      <NativeRadioController radios={args.radios} />\n    </div>\n  );\n};\n\nWithoutIcon.bind({});\n\nWithoutIcon.args = {\n  radios: [\n    {\n      title: \"Everyone\",\n      subtitle: \"Allow any user to send you DMs\",\n    },\n    {\n      title: \"People I Follow\",\n      subtitle: \"Only users you follow will be able to DM you\",\n    },\n    {\n      title: \"No One\",\n      subtitle: \"Prevent any user from sending you a DM\",\n    },\n  ],\n};\n"
  },
  {
    "path": "kibbeh/src/stories/NotificationElement/FollowNotification.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  FollowNotification,\n  FollowNotificationProps,\n} from \"../../ui/NotificationElement/FollowNotification\";\n\nimport src from \"../../img/avatar.png\";\n\nexport default {\n  title: \"Notification/FollowNotification\",\n  component: FollowNotification,\n};\n\nexport const Default: Story<FollowNotificationProps> = ({ ...props }) => (\n  <FollowNotification\n    {...props}\n    userAvatarSrc={props.userAvatarSrc || src}\n    username={props.username || \"johndoe\"}\n  />\n);\n\nDefault.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/NotificationElement/GenericNotification.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport {\n  GenericNotification,\n  GenericNotificationProps,\n} from \"../../ui/NotificationElement/GenericNotification\";\n\nexport default {\n  title: \"Notification/GenericNotification\",\n  component: GenericNotification,\n};\n\nexport const Default: Story<GenericNotificationProps> = ({ ...props }) => {\n  return <GenericNotification {...props} />;\n};\n\nDefault.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/NotificationElement/LiveNotification.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  LiveNotification,\n  LiveNotificationProps,\n} from \"../../ui/NotificationElement/LiveNotification\";\n\nexport default {\n  title: \"Notification/LiveNotification\",\n  component: LiveNotification,\n};\n\nexport const Default: Story<LiveNotificationProps> = ({ ...props }) => (\n  <LiveNotification {...props} username={props.username || \"johndoe\"} />\n);\n\nDefault.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/NotificationElement/NewRoomNotification.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  NewRoomNotification,\n  NewRoomNotificationProps,\n} from \"../../ui/NotificationElement/NewRoomNotification\";\n\nexport default {\n  title: \"Notification/NewRoomNotification\",\n  component: NewRoomNotification,\n};\n\nexport const Default: Story<NewRoomNotificationProps> = ({ ...props }) => (\n  <NewRoomNotification {...props} username={props.username || \"johndoe\"} />\n);\n\nDefault.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/NotificationsDropdown.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport {\n  NotificationsDropdown,\n  NotificationsDropdownProps,\n} from \"../ui/NotificationsDropdown\";\n\nexport default {\n  title: \"NotificationsDropdown\",\n};\n\nconst Notifications: NotificationsDropdownProps = {\n  data: [\n    {\n      type: \"follow\",\n      time: \"right now\",\n      username: \"John Doe\",\n      userAvatarSrc: \"https://picsum.photos/250/250\",\n    },\n    {\n      type: \"generic\",\n      notificationMsg: \"Lorem Ipsum\",\n      time: \"right now\",\n    },\n    {\n      type: \"live\",\n      username: \"John Doe\",\n      time: \"10 minutes ago\",\n    },\n    {\n      type: \"newroom\",\n      username: \"Have fun at dogehouse!\",\n      time: \"2 minutes ago\",\n    },\n  ],\n};\n\nexport const Main: Story<NotificationsDropdownProps> = ({ ...props }) => {\n  return <NotificationsDropdown {...props} {...Notifications} />;\n};\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/ProfileAbout.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { ProfileAbout, ProfileAboutProps } from \"../ui/ProfileAbout\";\n\nexport default {\n  title: \"ProfileAbout\",\n  component: ProfileAbout,\n};\n\nexport const Main: Story<ProfileAboutProps> = (props) => {\n  return (\n    <ProfileAbout\n      username=\"glenn_brenner\"\n      followers={15215}\n      following={672}\n      description=\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quis nunc sit pulvinar ut tellus sit tincidunt faucibus sapien. ⚡️\"\n      link=\"https://example.com\"\n      tags={[\n        {\n          icon: \"logo\",\n          children: \"Member since 13 March 2021\",\n        },\n        {\n          icon: \"dogeNitro\",\n          children: \"DogeNitro User\",\n        },\n        {\n          icon: \"dogeStaff\",\n          children: \"DogeHouse Staff\",\n        },\n        {\n          icon: \"dogeContributor\",\n          children: \"DogeHouse Contributor\",\n        },\n      ]}\n    />\n  );\n};\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/ProfileBlock.story.tsx",
    "content": "import { ProfileBlock } from \"../ui/ProfileBlock\";\nimport {\n  ScheduledRoomSummaryCardProps,\n  UpcomingRoomsCard,\n} from \"../ui/UpcomingRoomsCard\";\nimport { UserSummaryCard, UserSummaryCardProps } from \"../ui/UserSummaryCard\";\nimport { Story } from \"@storybook/react\";\nimport avatar from \"../img/avatar.png\";\nimport { addDays } from \"date-fns\";\n\nconst today = new Date();\n\nexport default {\n  title: \"ProfileBlock\",\n  component: ProfileBlock,\n};\n\ninterface MinimizedRoomCardProps {\n  name: string;\n  speakers: string[];\n  url: string;\n  timeElapsed: Duration;\n  myself: {\n    isSpeaker: boolean;\n    isMuted: boolean;\n    switchMuted(): void;\n    isDeafened: boolean;\n    switchDeafened(): void;\n    leave(): void;\n  };\n}\n\ninterface ProfileBlockProps {\n  userDetails?: UserSummaryCardProps;\n  connectedRoom?: MinimizedRoomCardProps;\n  upcomingRooms?: ScheduledRoomSummaryCardProps[];\n}\n\nconst upcoming: ScheduledRoomSummaryCardProps[] = [\n  {\n    onClick: () => {},\n    id: \"1\",\n    scheduledFor: today,\n    speakersInfo: {\n      avatars: [avatar],\n      speakers: [\"Dough Terry\"],\n    },\n    title: \"Live with u/DeepFuckingValue\",\n  },\n  {\n    onClick: () => {},\n    id: \"2\",\n    scheduledFor: addDays(today, 1),\n    speakersInfo: {\n      avatars: [avatar, avatar],\n      speakers: [\"Daniel Gailey\", \"Bennie Beyers\"],\n    },\n    title: \"Is Apple equipment worth it?\",\n  },\n  {\n    onClick: () => {},\n    id: \"3\",\n    scheduledFor: addDays(today, 2),\n    speakersInfo: {\n      avatars: [avatar, avatar, avatar],\n      speakers: [\"Jessika Beyer\", \"Jefferey Bosco\", \"Hang Ness\"],\n    },\n    title:\n      \"Starting your dream business in times of Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut ut ultricies turpis, vitae consectetur quam. Suspendisse venenatis justo justo, in pulvinar urna facilisis ut.\",\n  },\n];\n\nconst userDetail: UserSummaryCardProps = {\n  onClick: () => {},\n  avatarUrl: avatar,\n  id: \"1\",\n  displayName: \"Arnau Jiménez\",\n  username: \"@ajmnz\",\n  numFollowing: 89,\n  numFollowers: 3400,\n  bio:\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quis nunc sit pulvinar ut tellus sit tincidunt faucibus sapien. ⚡️\",\n  website: \"https://loremipsum.com\",\n  isOnline: true,\n  badges: [\n    { content: \"ƉC\", variant: \"primary\", color: \"white\" },\n    { content: \"ƉS\", variant: \"primary\", color: \"grey\" },\n  ],\n};\n\nexport const Main: Story<ProfileBlockProps> = ({\n  userDetails = userDetail,\n  upcomingRooms = upcoming,\n}) => (\n  <ProfileBlock\n    top={<UserSummaryCard {...userDetails} />}\n    bottom={\n      <UpcomingRoomsCard\n        onCreateScheduledRoom={() => {}}\n        rooms={upcomingRooms}\n      />\n    }\n  />\n);\n\nMain.bind({});\n\nexport const ConnectedRoom: Story<ProfileBlockProps> = ({\n  connectedRoom = {\n    name:\n      \"Senior Dev / Manager @ GoDaddy (TS/React/GQL) - Ask me whatever you want\",\n    speakers: [\"Terry Owen\", \"Grace Abraham\"],\n    url: \"/room/1829324\",\n    timeElapsed: { minutes: 58, seconds: 39 },\n    myself: {\n      isSpeaker: true,\n      isMuted: false,\n      switchMuted: () => {\n        // no-op\n      },\n      isDeafened: false,\n      switchDeafened: () => {\n        // no-op\n      },\n      leave: () => {\n        // no-op\n      },\n    },\n  },\n  upcomingRooms = upcoming,\n}) => (\n  <ProfileBlock\n    top={<ConnectedRoom connectedRoom={connectedRoom} />}\n    bottom={\n      <UpcomingRoomsCard\n        onCreateScheduledRoom={() => {}}\n        rooms={upcomingRooms}\n      />\n    }\n  />\n);\n\nConnectedRoom.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/ProfileHeader.story.tsx",
    "content": "import React from \"react\";\nimport { Meta, Story } from \"@storybook/react\";\nimport { ProfileHeader, ProfileHeaderProps } from \"../ui/ProfileHeader\";\nimport { Main as UserBadge } from \"./UserBadge.story\";\n\nconst Template: Story<ProfileHeaderProps> = ({ ...props }) => (\n  <ProfileHeader {...props} />\n);\n\nexport default {\n  title: \"ProfileHeader\",\n  component: ProfileHeader,\n} as Meta;\n\nexport const Main = Template.bind({});\nMain.args = {\n  displayName: \"Glen Brenner\",\n  username: \"@glen_brenner\",\n  children: <UserBadge />,\n};\n"
  },
  {
    "path": "kibbeh/src/stories/ProfileHeaderWrapper.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport profileCover from \"./img/profile-cover.png\";\n\nimport {\n  ProfileHeaderWrapper,\n  ProfileHeaderWrapperProps,\n} from \"../ui/ProfileHeaderWrapper\";\n\nexport default {\n  title: \"ProfileHeaderWrapper\",\n  component: ProfileHeaderWrapper,\n};\n\nconst ProfileHeaderChildren = () => (\n  <div className=\"text-accent\">Profile header content</div>\n);\n\nProfileHeaderWrapper.defaultProps = {\n  coverUrl: profileCover,\n  children: <ProfileHeaderChildren />,\n};\n\nexport const Main: Story<ProfileHeaderWrapperProps> = ({ ...props }) => (\n  <ProfileHeaderWrapper {...props} />\n);\n"
  },
  {
    "path": "kibbeh/src/stories/ProfileTabs.story.tsx",
    "content": "import { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport { Story } from \"@storybook/react\";\nimport { ProfileTabs, ProfileTabsProps } from \"../ui/ProfileTabs\";\n\nexport default {\n  title: \"ProfileTabs\",\n  component: ProfileTabs,\n};\n\nconst user: UserWithFollowInfo = {\n  username: \"Amitoj\",\n  online: true,\n  lastOnline: \"\",\n  id: \"awdawd69\",\n  bio: \"\",\n  displayName: \"Amitoj\",\n  avatarUrl: \"\",\n  bannerUrl: \"\",\n  numFollowers: 696969,\n  numFollowing: 0,\n  staff: true,\n  contributions: 100,\n};\n\nexport const ScheduledTabActive: Story<ProfileTabsProps> = ({}) => {\n  return (\n    <div className=\"space-y-4 space-x-2\">\n      <ProfileTabs user={user} />\n    </div>\n  );\n};\n\nScheduledTabActive.bind({});\n\nexport const RoomsTabActive: Story<ProfileTabsProps> = ({}) => {\n  return (\n    <div className=\"space-y-4 space-x-2\">\n      <ProfileTabs user={user} />\n    </div>\n  );\n};\n\nRoomsTabActive.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/RightHeader.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport RightHeader, { RightHeaderProps } from \"../ui/header/RightHeader\";\n\nexport default {\n  title: \"RightHeader\",\n  component: RightHeader,\n};\n\nexport const Main: Story<RightHeaderProps> = ({ ...props }) => (\n  <RightHeader {...props} />\n);\n"
  },
  {
    "path": "kibbeh/src/stories/RoomAvatar.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { RoomCardHeading, RoomCardHeadingProps } from \"../ui/RoomCardHeading\";\nimport { SolidTime } from \"../icons\";\nimport { RoomAvatar } from \"../ui/RoomAvatar\";\nimport src from \"../img/avatar.png\";\nimport RegularDoge from \"../img/regular-doge.png\";\n\nexport default {\n  title: \"RoomAvatar\",\n};\n\nexport const Regular: Story<RoomCardHeadingProps> = () => {\n  return <RoomAvatar src={src} username=\"Terry\" />;\n};\n\nexport const LongName: Story<RoomCardHeadingProps> = () => {\n  return <RoomAvatar src={src} username=\"TerryTerryTerry\" />;\n};\n\nexport const ActiveSpeaker: Story<RoomCardHeadingProps> = () => {\n  return <RoomAvatar src={src} username=\"Terry\" activeSpeaker />;\n};\n\nexport const Mod: Story<RoomCardHeadingProps> = () => {\n  return (\n    <RoomAvatar\n      src={src}\n      username=\"Terry\"\n      flair={\n        <img\n          src={RegularDoge}\n          alt=\"room mod\"\n          style={{ marginLeft: 4, marginTop: 2 }}\n          className={`w-3 h-3 ml-1`}\n        />\n      }\n    />\n  );\n};\n\nexport const Muted: Story<RoomCardHeadingProps> = () => {\n  return <RoomAvatar src={src} username=\"Terry\" muted />;\n};\n\nexport const Deafened: Story<RoomCardHeadingProps> = () => {\n  return <RoomAvatar src={src} username=\"Terry\" deafened />;\n};\n"
  },
  {
    "path": "kibbeh/src/stories/RoomCard.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { addDays, addHours, addSeconds } from \"date-fns\";\n\nimport { RoomCard, RoomCardProps } from \"../ui/RoomCard\";\nimport { GbFlagIcon as Icon } from \"./utils/GbFlagIcon\";\n\nexport default {\n  title: \"RoomCard\",\n  component: RoomCard,\n};\n\nexport const Live: Story<RoomCardProps> = ({\n  title = \"Live with u/DeepFuckingValue\",\n  subtitle = \"Doug Terry, Denae Augustine, DeepFuckingValue\",\n  listeners = 14400,\n  scheduledFor = new Date(Date.now() - 100),\n  tags = [<Icon key={0} />, \"#interview\", \"#GME\"],\n}) => (\n  <div className=\"flex\" style={{ width: 640 }}>\n    <RoomCard\n      avatars={[]}\n      title={title}\n      subtitle={subtitle}\n      listeners={listeners}\n      scheduledFor={scheduledFor}\n      tags={tags}\n    />\n  </div>\n);\n\nLive.bind({});\n\nexport const StartsIn2Secs: Story<RoomCardProps> = ({\n  title = \"Live with u/DeepFuckingValueLive with u/DeepFuckingValue Live with u/DeepFuckingValue Live with u/DeepFuckingValue Live with u/DeepFuckingValue\",\n  subtitle = \"Doug Terry, Denae Augustine, DeepFuckingValue\",\n  listeners = 1300,\n  scheduledFor = addSeconds(Date.now(), 2),\n  tags = [<Icon key={0} />, \"#interview\", \"#GME\"],\n}) => (\n  <div className=\"flex\" style={{ width: 640 }}>\n    <RoomCard\n      avatars={[]}\n      title={title}\n      subtitle={subtitle}\n      listeners={listeners}\n      scheduledFor={scheduledFor}\n      tags={tags}\n    />\n  </div>\n);\n\nStartsIn2Secs.bind({});\n\nexport const StartsIn2Hours: Story<RoomCardProps> = ({\n  title = \"Why CI & CD is important when working with a team\",\n  subtitle = \"Terry Owen, Grace Abraham, Richard Cameron\",\n  listeners = 0,\n  scheduledFor = addHours(Date.now(), 2),\n  tags = [<Icon key={0} />, \"#tech\", \"#CI/CD\", \"webinar\"],\n}) => (\n  <div className=\"flex\" style={{ width: 640 }}>\n    <RoomCard\n      title={title}\n      avatars={[]}\n      subtitle={subtitle}\n      listeners={listeners}\n      scheduledFor={scheduledFor}\n      tags={tags}\n    />\n  </div>\n);\n\nStartsIn2Hours.bind({});\n\nexport const StartsIn2Days: Story<RoomCardProps> = ({\n  title = \"Why CI & CD is important when working with a team\",\n  subtitle = \"Terry Owen, Grace Abraham, Richard Cameron\",\n  listeners = 0,\n  scheduledFor = addDays(Date.now(), 2),\n  tags = [<Icon key={0} />, \"#tech\", \"#CI/CD\", \"webinar\"],\n}) => (\n  <div className=\"flex\" style={{ width: 640 }}>\n    <RoomCard\n      avatars={[]}\n      title={title}\n      subtitle={subtitle}\n      listeners={listeners}\n      scheduledFor={scheduledFor}\n      tags={tags}\n    />\n  </div>\n);\n\nStartsIn2Days.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/RoomCardHeading.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { RoomCardHeading, RoomCardHeadingProps } from \"../ui/RoomCardHeading\";\nimport { SolidTime } from \"../icons\";\n\nexport default {\n  title: \"RoomCardHeading\",\n};\n\nconst LiveHeading: Story<RoomCardHeadingProps> = ({ icon, text }) => {\n  return <RoomCardHeading text={text || \"The developers hangout\"} />;\n};\n\nconst ScheduledHeading: Story<RoomCardHeadingProps> = ({ icon, text }) => {\n  return (\n    <RoomCardHeading\n      icon={icon || <SolidTime />}\n      text={text || \"Live with u/DeepFuckingValue\"}\n    />\n  );\n};\n\nexport const Current = LiveHeading.bind({});\nexport const Scheduled = ScheduledHeading.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/RoomCardParticipants.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport {\n  RoomCardParticipants,\n  RoomCardParticipantsProps,\n} from \"../ui/RoomCardParticipants\";\nimport { SolidTime } from \"../icons\";\n\nexport default {\n  title: \"RoomCardParticipants\",\n};\n\nconst TheRoomCardParticipants: Story<RoomCardParticipantsProps> = ({\n  users,\n}) => {\n  return (\n    <div className=\"flex\" style={{ width: \"200px\" }}>\n      <RoomCardParticipants\n        users={\n          users\n            ? users\n            : [\n                {\n                  name: \"GoldyyDev\",\n                  picture:\n                    \"https://avatars.githubusercontent.com/u/55450464?v=4\",\n                },\n                {\n                  name: \"ofsho\",\n                  picture:\n                    \"https://avatars.githubusercontent.com/u/60016719?v=4\",\n                },\n              ]\n        }\n      />\n    </div>\n  );\n};\n\nexport const Main = TheRoomCardParticipants.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/RoomHeader.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport { RoomCardHeadingProps } from \"../ui/RoomCardHeading\";\nimport { RoomHeader } from \"../ui/RoomHeader\";\n\nexport default {\n  title: \"RoomHeader\",\n};\n\nconst TheRoomHeader: Story<RoomCardHeadingProps> = () => {\n  return (\n    <div className=\"flex flex-col space-y-5\">\n      <RoomHeader\n        title=\"Why CI/CD is important when working with a team\"\n        description=\"\"\n        names={[\"Terry Owen\"]}\n      />\n    </div>\n  );\n};\n\nexport const Main = TheRoomHeader.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/RoomPanelIconBar.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport { RoomPanelIconBar } from \"../ui/RoomPanelIconBar\";\n\nexport default {\n  title: \"RoomPanelIconBar\",\n};\n\nexport const Main: Story = () => {\n  return (\n    <RoomPanelIconBar\n      onToggleChat={() => {}}\n      onLeaveRoom={() => {}}\n      onInvitePeopleToRoom={() => {}}\n      mute={{ isMuted: false, onMute: () => {} }}\n      deaf={{ isDeaf: false, onDeaf: () => {} }}\n      onRoomSettings={() => {}}\n    />\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/stories/RoomSectionHeader.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport { RoomCardHeadingProps } from \"../ui/RoomCardHeading\";\nimport { RoomSectionHeader } from \"../ui/RoomSectionHeader\";\n\nexport default {\n  title: \"RoomSectionHeader\",\n};\n\nconst TheRoomSectionHeader: Story<RoomCardHeadingProps> = () => {\n  return (\n    <div className=\"flex flex-col space-y-5\">\n      <RoomSectionHeader tagText=\"17\" title=\"Speakers\" />\n    </div>\n  );\n};\n\nexport const Main = TheRoomSectionHeader.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/Search/GlobalSearch.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  GlobalSearch,\n  GlobalSearchProps,\n  HistoryItem,\n  SearchResultItem,\n} from \"../../ui/Search/GlobalSearch\";\n\nimport avatar from \"../../img/avatar.png\";\nimport { Room, User } from \"@dogehouse/kebab\";\n\nexport default {\n  title: \"Search/GlobalSearch\",\n  component: GlobalSearch,\n};\n\nconst history: HistoryItem[] = [\n  { id: \"1\", term: \"javascript\" },\n  { id: \"2\", term: \"react graphql\" },\n  { id: \"3\", term: \"español\" },\n  { id: \"4\", term: \"elon musk interview\" },\n  { id: \"5\", term: \"elon muk\" },\n];\n\nconst searchResults: SearchResultItem[] = [\n  {\n    id: \"1\",\n    name: \"The developer’s hangout\",\n    // hosts: [\"Terry Owen\", \"Grace Abraham\"],\n    numPeopleInside: 355,\n  } as Room,\n  {\n    id: \"2\",\n    displayName: \"The Real Anthony\",\n    username: \"@anthonytheone\",\n    online: true,\n    avatarUrl: avatar,\n  } as User,\n];\n\nconst globalSearchProps: GlobalSearchProps = {\n  history,\n  searchResults,\n};\n\nexport const Main: Story<GlobalSearchProps> = ({ ...props }) => (\n  <GlobalSearch\n    history={props.history || globalSearchProps.history}\n    searchResults={props.searchResults || searchResults}\n  />\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/Search/SearchBar.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { SearchBar, SearchBarProps } from \"../../ui/Search/SearchBar\";\nimport { toStr } from \"../utils/toStr\";\nimport { toBoolean } from \"../utils/toBoolean\";\n\nexport default {\n  title: \"Search/SearchBar\",\n  argTypes: {\n    onChange: { action: \"changed\" },\n  },\n};\n\nconst TheSearchBar: Story<SearchBarProps> = ({ placeholder, ...props }) => {\n  return (\n    <SearchBar\n      placeholder={placeholder || \"Search for rooms, users or categories\"}\n      {...props}\n    />\n  );\n};\n\nexport const Main = TheSearchBar.bind({});\n\nMain.argTypes = {\n  value: toStr(),\n  placeholder: toStr(),\n  mobile: toBoolean(),\n  isLoading: toBoolean(),\n};\n"
  },
  {
    "path": "kibbeh/src/stories/Search/SearchHistory.story.tsx",
    "content": "import React from \"react\";\nimport SearchHistoryComponent, {\n  SearchHistoryProps,\n} from \"../../ui/Search/SearchHistory\";\n\nexport default {\n  title: \"Search/SearchHistory\",\n  argTypes: {\n    onClickToDeleteSearchHistory: { action: \"clicked\" },\n    searchText: { defaultValue: \"javascript\" },\n  },\n};\n\nexport const SearchHistory = (props: SearchHistoryProps) => {\n  return <SearchHistoryComponent {...props} />;\n};\n"
  },
  {
    "path": "kibbeh/src/stories/Search/SearchOverlay.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  SearchOverlay,\n  SearchOverlayProps,\n} from \"../../ui/Search/SearchOverlay\";\nimport { SearchBar } from \"../../ui/Search/SearchBar\";\n\nexport default {\n  title: \"Search/SearchOverlay\",\n  component: SearchOverlay,\n};\n\nexport const Main: Story<SearchOverlayProps> = ({ children }) => (\n  <SearchOverlay>{children || <SearchBar />}</SearchOverlay>\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/Search/SearchResult/RoomSearchResult.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  RoomSearchResult,\n  RoomSearchResultProps,\n} from \"../../../ui/Search/SearchResult\";\n\nexport default {\n  title: \"Search/SearchResult/RoomSearchResult\",\n  component: RoomSearchResult,\n};\n\nconst room = {\n  displayName: \"The developer’s hangout\",\n  userCount: 355,\n  hosts: [\"Terry Owen\", \"Grace Abraham\"],\n};\n\nexport const Main: Story<RoomSearchResultProps> = ({ ...props }) => (\n  <RoomSearchResult {...props} room={props.room || room} />\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/Search/SearchResult/UserSearchResult.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport {\n  UserSearchResult,\n  UserSearchResultProps,\n} from \"../../../ui/Search/SearchResult\";\n\nexport default {\n  title: \"Search/SearchResult/UserSearchResult\",\n  component: UserSearchResult,\n};\n\nimport avatar from \"../../../img/avatar.png\";\n\nconst user = {\n  avatar,\n  displayName: \"The Real Anthony\",\n  username: \"@anthonytheone\",\n  isOnline: true,\n};\n\nexport const Main: Story<UserSearchResultProps> = ({ ...props }) => (\n  <UserSearchResult {...props} user={props.user || user} />\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/SettingsDropdown.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { SettingsDropdown } from \"../ui/SettingsDropdown\";\n\nexport default {\n  title: \"SettingsDropdown\",\n};\n\nconst TheSettingsDropdown: Story = () => {\n  return (\n    <SettingsDropdown\n      onActionButtonClicked={() => {}}\n      onCloseDropdown={() => {}}\n      // @todo\n      user={{} as any}\n    />\n  );\n};\n\nexport const Main = TheSettingsDropdown.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/SettingsIcon.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { SettingsIcon, SettingsIconProps } from \"../ui/SettingsIcon\";\nimport { SolidUser, SolidCaretRight, OutlineGlobe } from \"../icons\";\n\nexport default {\n  title: \"SettingsIcon\",\n  component: SettingsIcon,\n};\n\nexport const Default: Story<SettingsIconProps> = ({ ...props }) => {\n  return (\n    <SettingsIcon\n      {...props}\n      icon={props.icon || <SolidUser className=\"text-primary-100\" />}\n      label={props.label || \"profile\"}\n    />\n  );\n};\n\nexport const WithTrailingIcon: Story<SettingsIconProps> = ({ ...props }) => {\n  return (\n    <SettingsIcon\n      {...props}\n      icon={props.icon || <OutlineGlobe className=\"text-primary-100\" />}\n      label={props.label || \"Language\"}\n      trailingIcon={\n        props.trailingIcon || <SolidCaretRight className=\"text-primary-100\" />\n      }\n    />\n  );\n};\n\nDefault.bind({});\nWithTrailingIcon.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/SettingsItemButton.story.tsx",
    "content": "import React, { ReactNode } from \"react\";\nimport { Story } from \"@storybook/react\";\nimport {\n  SettingsItemButton,\n  SettingsItemButtonProps,\n} from \"../ui/SettingsItemButton\";\n\nexport default {\n  title: \"SettingsItemButton\",\n  component: SettingsItemButton,\n};\n\nexport const Main: Story<SettingsItemButtonProps> = ({\n  text = \"This is a one way operation, once you delete your account there is no going back. Please be certain.\",\n  buttonText = \"Delete your account\",\n  onClick = (e: React.MouseEvent | React.TouchEvent) => {\n    e.preventDefault();\n  },\n}) => (\n  <SettingsItemButton text={text} buttonText={buttonText} onClick={onClick} />\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/SettingsWrapper.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { SettingsWrapper, SettingsWrapperProps } from \"../ui/SettingsWrapper\";\n\nexport default {\n  title: \"SettingsWrapper\",\n  component: SettingsWrapper,\n};\n\nSettingsWrapper.defaultProps = {\n  children: <></>,\n};\n\nexport const Main: Story<SettingsWrapperProps> = ({ ...props }) => (\n  <SettingsWrapper {...props} />\n);\n"
  },
  {
    "path": "kibbeh/src/stories/Tag.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { Tag } from \"../ui/Tag\";\nimport { toStr } from \"./utils/toStr\";\nimport { GbFlagIcon as Icon } from \"./utils/GbFlagIcon\";\n\nexport default {\n  title: \"Tag\",\n  argTypes: {\n    onChange: { action: \"changed\" },\n  },\n};\n\nconst TheTag: Story = ({ tag = \"interview\" }) => {\n  return (\n    <>\n      <span className={`mr-2`}>\n        <Tag>{tag}</Tag>\n      </span>\n      <Tag>\n        <Icon />\n      </Tag>\n    </>\n  );\n};\n\nexport const Main = TheTag.bind({});\n\nMain.argTypes = {\n  tag: toStr(),\n};\n\nexport const TagGlowing: Story = ({ tag = \"interview\" }) => {\n  return (\n    <>\n      <span className={`mr-2`}>\n        <Tag glow>{tag}</Tag>\n      </span>\n      <Tag glow>\n        <Icon />\n      </Tag>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/stories/UpcomingRoomCardLg.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport avatar from \"./img/user-avatar-sm.png\";\nimport avatar1 from \"./img/user-avatar-sm1.png\";\n\nimport {\n  UpcomingRoomCardLg,\n  UpcomingRoomCardLgProps,\n} from \"../ui/UpcomingRoomCardLg\";\n\nexport default {\n  title: \"UpcomingRoomCardLg\",\n  component: UpcomingRoomCardLg,\n};\n\nUpcomingRoomCardLg.defaultProps = {\n  title:\n    \"Japanese vs European cars (w/ ChrisFix) and with a very very vary very long text \",\n  hosts: [\n    {\n      avatarUrl: avatar,\n      bio: \"\",\n      id: \"1\",\n      displayName: \"Glen Brenner\",\n      lastOnline: \"\",\n      numFollowers: 0,\n      numFollowing: 0,\n      bannerUrl: \"\",\n      online: true,\n      username: \"gle1\",\n      staff: false,\n      contributions: 0,\n    },\n    {\n      bannerUrl: \"\",\n      avatarUrl: avatar1,\n      bio: \"\",\n      id: \"2\",\n      displayName: \"ChrisFix\",\n      lastOnline: \"\",\n      numFollowers: 0,\n      numFollowing: 0,\n      online: true,\n      username: \"chr1\",\n      staff: false,\n      contributions: 0,\n    },\n  ],\n  date: 1616187600000,\n  descriptions:\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Auctor risus lectus blandit posuere mauris feugiat gravida. Risus morbi commodo mattis molestie adipiscing tortor, mattis porta ullamcorper. Lorem orci convallis egestas commodo mauris, dui vestibulum, semper faucibus. Est quam lobortis proin non neque.\",\n  tags: [\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n    \"#cars\",\n    \"#debate\",\n  ],\n};\n\nexport const Main: Story<UpcomingRoomCardLgProps> = ({ ...props }) => (\n  <div style={{ width: \"640px\" }}>\n    <UpcomingRoomCardLg {...props} />\n  </div>\n);\n"
  },
  {
    "path": "kibbeh/src/stories/UpcomingRoomsCard.story.tsx",
    "content": "import React from \"react\";\n\nimport { Story } from \"@storybook/react\";\nimport {\n  ScheduledRoomSummaryCard,\n  ScheduledRoomSummaryCardProps,\n  UpcomingRoomsCard,\n  UpcomingRoomsCardProps,\n} from \"../ui/UpcomingRoomsCard\";\n\nimport src from \"../img/avatar.png\";\nimport { addDays } from \"date-fns\";\n\nconst today = new Date();\n\nconst upcomingRooms: ScheduledRoomSummaryCardProps[] = [\n  {\n    onClick: () => {},\n    id: \"1\",\n    scheduledFor: today,\n    speakersInfo: {\n      avatars: [src],\n      speakers: [\"Dough Terry\"],\n    },\n    title: \"Live with u/DeepFuckingValue\",\n  },\n  {\n    onClick: () => {},\n    id: \"2\",\n    scheduledFor: addDays(today, 1),\n    speakersInfo: {\n      avatars: [src, src],\n      speakers: [\"Daniel Gailey\", \"Bennie Beyers\"],\n    },\n    title: \"Is Apple equipment worth it?\",\n  },\n  {\n    onClick: () => {},\n    id: \"3\",\n    scheduledFor: addDays(today, 2),\n    speakersInfo: {\n      avatars: [src, src, src],\n      speakers: [\"Jessika Beyer\", \"Jefferey Bosco\", \"Hang Ness\"],\n    },\n    title:\n      \"Starting your dream business in times of Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut ut ultricies turpis, vitae consectetur quam. Suspendisse venenatis justo justo, in pulvinar urna facilisis ut.\",\n  },\n];\n\nexport default {\n  title: \"UpcomingRoomsCard\",\n  component: UpcomingRoomsCard,\n};\n\nexport const ScheduledRoom: Story<ScheduledRoomSummaryCardProps> = ({\n  ...props\n}) => (\n  <div className=\"flex\" style={{ width: 365 }}>\n    <ScheduledRoomSummaryCard {...props} {...upcomingRooms[2]} />\n  </div>\n);\n\nScheduledRoom.bind({});\n\nexport const Main: Story<UpcomingRoomsCardProps> = ({ ...props }) => (\n  <div className=\"flex\" style={{ width: 365 }}>\n    <UpcomingRoomsCard {...props} rooms={upcomingRooms} />\n  </div>\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/UserAvatar/MultipleUsers.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { MultipleUsers, AvatarProps } from \"../../ui/UserAvatar/MultipleUsers\";\n\nimport src from \"../../img/avatar.png\";\n\nexport default {\n  title: \"UserAvatar/MultipleUsers\",\n  component: MultipleUsers,\n};\n\nexport const Default: Story<AvatarProps> = ({ ...props }) => (\n  <MultipleUsers {...props} srcArray={[src, src, src]} />\n);\n\nDefault.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/UserAvatar/SingleUser.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\n\nimport { SingleUser, AvatarProps } from \"../../ui/UserAvatar/SingleUser\";\n\nimport src from \"../../img/avatar.png\";\n\nexport default {\n  title: \"UserAvatar/SingleUser\",\n  component: SingleUser,\n};\n\nexport const Default: Story<AvatarProps> = ({ ...props }) => (\n  <SingleUser {...props} src={src} />\n);\n\nexport const Online: Story<AvatarProps> = ({ ...props }) => (\n  <SingleUser {...props} src={src} isOnline />\n);\n\nexport const Muted: Story<AvatarProps> = ({ ...props }) => (\n  <SingleUser {...props} src={src} muted />\n);\n\nexport const Deafened: Story<AvatarProps> = ({ ...props }) => (\n  <SingleUser {...props} src={src} deafened />\n);\n\nDefault.bind({});\nOnline.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/UserBadge.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport { UserBadge } from \"../ui/UserBadge\";\nimport { SolidDogenitro } from \"../icons\";\n\nexport default {\n  title: \"UserBadge\",\n};\n\nconst TheUserBadge: Story = () => {\n  return (\n    <div className=\"flex flex-row\">\n      <div className=\"flex\">\n        <UserBadge>ƉC</UserBadge>\n      </div>\n      <div className=\"flex\">\n        <UserBadge>ƉS</UserBadge>\n      </div>\n      <div className=\"flex\">\n        <UserBadge variant=\"secondary\">\n          <SolidDogenitro style={{ color: \"#FFF\" }} width={12} />\n        </UserBadge>\n      </div>\n    </div>\n  );\n};\n\nexport const Main = TheUserBadge.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/UserBadgeLg.story.tsx",
    "content": "import React from \"react\";\nimport { Meta, Story } from \"@storybook/react\";\nimport { UserBadgeLg, UserBadgeLgProps } from \"../ui/UserBadgeLg\";\n\nconst Template: Story<UserBadgeLgProps> = ({ icon, label }) => (\n  <UserBadgeLg icon={icon}>{label}</UserBadgeLg>\n);\n\nexport default {\n  title: \"UserBadgeLg\",\n  component: UserBadgeLg,\n  args: {\n    label: \"DogeHouse User\",\n  },\n} as Meta;\n\nexport const Primary = Template.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/UserSummaryCard.story.tsx",
    "content": "import React from \"react\";\nimport { Story } from \"@storybook/react\";\nimport avatar from \"../img/avatar.png\";\nimport { UserSummaryCard, UserSummaryCardProps } from \"../ui/UserSummaryCard\";\nimport { SolidDogenitro } from \"../icons\";\n\nexport default {\n  title: \"UserSummaryCard\",\n  component: UserSummaryCard,\n};\n\nconst userSummary: UserSummaryCardProps = {\n  onClick: () => {},\n  avatarUrl: avatar,\n  id: \"1\",\n  displayName: \"Arnau Jiménez\",\n  username: \"@ajmnz\",\n  numFollowing: 89,\n  numFollowers: 3400,\n  bio:\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quis nunc sit pulvinar ut tellus sit tincidunt faucibus sapien. ⚡️\",\n  website: \"https://loremipsum.com\",\n  isOnline: true,\n  badges: [\n    { content: \"ƉC\", variant: \"primary\", color: \"white\" },\n    { content: \"ƉS\", variant: \"primary\", color: \"white\" },\n    {\n      content: <SolidDogenitro width={12} style={{ color: \"#fff\" }} />,\n      variant: \"secondary\",\n      color: \"white\",\n    },\n  ],\n};\n\nexport const Main: Story<UserSummaryCardProps> = ({ ...props }) => (\n  <UserSummaryCard {...props} {...userSummary} />\n);\n\nMain.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/UserWideButton.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport { sampleBaseUser } from \"./data/BaseUser\";\nimport { UserWideButton, UserWideButtonInfoProps } from \"../ui/UserWideButton\";\n\nexport default {\n  title: \"UserWideButton\",\n  component: UserWideButton,\n};\n\nexport const Main: Story<UserWideButtonInfoProps> = ({ ...props }) => {\n  return <UserWideButton {...props} user={sampleBaseUser} />;\n};\n"
  },
  {
    "path": "kibbeh/src/stories/VerticalUserInfo.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport { RoomCardHeadingProps } from \"../ui/RoomCardHeading\";\nimport { VerticalUserInfo } from \"../ui/VerticalUserInfo\";\nimport { sampleBaseUser } from \"./data/BaseUser\";\n\nexport default {\n  title: \"VerticalUserInfo\",\n};\n\nexport const Main: Story<RoomCardHeadingProps> = () => {\n  return <VerticalUserInfo user={sampleBaseUser} />;\n};\n"
  },
  {
    "path": "kibbeh/src/stories/VolumeIndicator.story.tsx",
    "content": "import React from \"react\";\nimport { Meta, Story } from \"@storybook/react\";\nimport { VolumeIndicator, VolumeIndicatorProps } from \"../ui/VolumeIndicator\";\n\nconst Template: Story<VolumeIndicatorProps> = ({ volume, bars }) => (\n  <VolumeIndicator volume={volume} bars={bars}></VolumeIndicator>\n);\n\nexport default {\n  title: \"VolumeIndicator\",\n  component: VolumeIndicator,\n  args: {\n    volume: 50,\n    bars: 24,\n  },\n} as Meta;\n\nexport const Primary = Template.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/data/BaseUser.tsx",
    "content": "export const sampleBaseUser = {\n  username: \"benawad\",\n  online: false,\n  numFollowing: 0,\n  numFollowers: 0,\n  lastOnline: \"2021-03-22T15:47:51.100075Z\",\n  id: \"79651f9a-5725-4016-a560-578d098f1071\",\n  displayName: \"Ben Awad\",\n  bio: \"Software Consultant | YouTuber \\r\\n| React.js and GraphQL Enthusiast \",\n  avatarUrl: \"https://avatars.githubusercontent.com/u/7872329?v=4\",\n  bannerUrl: \"https://avatars.githubusercontent.com/u/7872329?v=4\",\n  staff: false,\n  contributions: 0,\n} as const;\n"
  },
  {
    "path": "kibbeh/src/stories/mobile/FeaturedRoomCardAvatars.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport {\n  FeaturedRoomCardAvatars,\n  FeaturedRoomCardAvatarsProps,\n} from \"../../ui/mobile/FeaturedRoomCardAvatars\";\nimport src from \"../../img/avatar.png\";\n\nexport default {\n  title: \"FeaturedRoomCardAvatars\",\n  component: FeaturedRoomCardAvatars,\n};\n\nconst Avatars: Story<FeaturedRoomCardAvatarsProps> = () => {\n  return <FeaturedRoomCardAvatars avatars={[src, src]} />;\n};\n\nconst One: Story<FeaturedRoomCardAvatarsProps> = () => {\n  return <FeaturedRoomCardAvatars avatars={[src]} />;\n};\n\nexport const Main = Avatars.bind({});\nexport const OneAvatar = One.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/mobile/FeaturedRoomCardHosts.story.tsx",
    "content": "import { Story } from \"@storybook/react\";\nimport React from \"react\";\nimport src from \"../../img/avatar.png\";\nimport {\n  FeaturedRoomCardHosts,\n  FeaturedRoomCardHostsProps,\n} from \"../../ui/mobile/FeaturedRoomCardHosts\";\n\nexport default {\n  title: \"FeaturedRoomCardHosts\",\n  component: FeaturedRoomCardHosts,\n};\n\nconst Hosts: Story<FeaturedRoomCardHostsProps> = () => {\n  return (\n    <FeaturedRoomCardHosts\n      avatars={[src, src]}\n      names={[\"Marcus Bloch\", \"Don Velez\"]}\n    />\n  );\n};\n\nconst One: Story<FeaturedRoomCardHostsProps> = () => {\n  return <FeaturedRoomCardHosts avatars={[src]} names={[\"Marcus Bloch\"]} />;\n};\n\nexport const Main = Hosts.bind({});\nexport const OneHost = One.bind({});\n"
  },
  {
    "path": "kibbeh/src/stories/utils/GbFlagIcon.tsx",
    "content": "import React from \"react\";\n\nexport const GbFlagIcon: React.FC = () => {\n  return (\n    <svg\n      width=\"12\"\n      height=\"9\"\n      version=\"1.1\"\n      viewBox=\"0 0 50 30\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <g>\n        <rect x=\"9.8296e-6\" y=\"-1.8681e-6\" width=\"50\" height=\"30\" fill=\"#fff\" />\n        <path\n          d=\"m-7e-6 -5e-7v2.3321l12.779 7.6678 3.8871-1e-4zm22 0v11.999h-22v5.9961h22v12.005h5.9998v-12.005h22v-5.9961h-22v-11.999zm24.112 0-16.113 9.6675v0.33232h3.3336l16.666-9.9998zm-29.488 20-16.625 10h3.8873l16.112-9.668v-0.33203zm16.709 0 16.666 10v-2.3323l-12.777-7.6677z\"\n          fill=\"#c8102e\"\n        />\n        <path\n          d=\"m5.85-5e-7 14.15 8.5002v-8.5002zm24.15 0v8.5016l14.17-8.5016zm-30 3.4985v6.5006l10.836 7.55e-4zm50 2.1e-4 -10.835 6.5011h10.835zm-50 16.501v6.5021l10.835-6.5021zm39.169 0 10.831 6.576v-6.576zm-9.1689 1.4986v8.5014h14.17zm-10 3.99e-4 -14.169 8.501h14.169z\"\n          fill=\"#012169\"\n        />\n      </g>\n    </svg>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/stories/utils/toBoolean.ts",
    "content": "export const toBoolean = () => ({\n  control: {\n    type: \"boolean\",\n  },\n});\n"
  },
  {
    "path": "kibbeh/src/stories/utils/toEnum.ts",
    "content": "export const toEnum = <T>(arr: T[]) => ({\n  control: {\n    type: \"inline-radio\",\n    options: arr,\n  },\n});\n"
  },
  {
    "path": "kibbeh/src/stories/utils/toStr.ts",
    "content": "export const toStr = () => ({\n  control: {\n    type: \"text\",\n  },\n});\n"
  },
  {
    "path": "kibbeh/src/styles/banner-button.css",
    "content": ".banner-btn:hover {\n  background-color: #4862c0;\n}\n.banner-btn {\n  border: solid #4158ac 1.5px;\n}\n"
  },
  {
    "path": "kibbeh/src/styles/date-time-picker.css",
    "content": ".MuiTabs-root .MuiTabs-indicator {\n  background-color: var(--color-accent);\n}\n"
  },
  {
    "path": "kibbeh/src/styles/electron-header.css",
    "content": ".electron-header {\n  width: 100%;\n  height: 30px;\n  background-color: var(--color-primary-700);\n  margin-bottom: 30px;\n  display: inline-flex;\n  align-items: stretch;\n  padding-left: 10px;\n  user-select: none;\n  position: fixed;\n  top: 0;\n}\n\n.default-desktop-layout {\n  margin-top: 30px;\n}\n\n.header-image-cont {\n  margin-top: 6px;\n  display: block;\n  -webkit-app-region: drag;\n}\n.header-drag-region {\n  -webkit-app-region: drag;\n}\n.header-title {\n  margin-top: 5px;\n  padding-left: 5px;\n  color: #fd4d4d;\n  font-size: small;\n  font-weight: 700;\n  font-family: \"Inter\";\n  -webkit-app-region: drag;\n}\n\n.header-icons {\n  color: #ffffff;\n}\n.header-icons-mac {\n  margin-top: 9.5px;\n  margin-left: 5px;\n}\n\n.red-mac-button {\n  color: #4c0000;\n  background-color: #fd605b;\n}\n.green-mac-button {\n  color: #006500;\n  background-color: #32ca51;\n}\n.yellow-mac-button {\n  color: #975500;\n  background-color: #fdbc47;\n}\n\n.mac-buttons {\n  height: 12px;\n  width: 12px;\n}\n"
  },
  {
    "path": "kibbeh/src/styles/globals.css",
    "content": "@import url(\"https://cdnjs.cloudflare.com/ajax/libs/inter-ui/3.18.0/inter.css\");\n@import \"tailwindcss/base\";\n@import \"tailwindcss/utilities\";\n\nh1,\nh2,\nh3,\nh4,\np {\n  line-height: 1.6;\n}\n\nh1,\nh2,\nh3,\nh4,\np.bold {\n  font-weight: 700;\n}\n\n:root {\n  font-size: 14px;\n\n  --color-button-text: #fff;\n  --color-primary-100: #dee3ea;\n  --color-primary-200: #b2bdcd;\n  --color-primary-300: #5d7290;\n  --color-primary-600: #323d4d;\n  --color-primary-700: #242c37;\n  --color-primary-800: #151a21;\n  --color-primary-900: #0b0e11;\n  --color-secondary-washed-out: #879eed;\n  --color-secondary: #5575e7;\n  --color-accent-glow: rgba(253, 77, 77, 0.3);\n  --color-accent: #fd4d4d;\n  --color-accent-hover: #fd6868;\n  --color-accent-disabled: #f5bfbf;\n  --screen-height-reduction: 0px;\n  --color-primary-100-translucent: rgba(222, 227, 234, 0.15);\n}\n\nh1 {\n  font-size: 4rem;\n}\n\nh2 {\n  font-size: 2.8rem;\n}\n\nh3 {\n  font-size: 2rem;\n}\n\nh4 {\n  font-size: 1.4rem;\n}\n\np {\n  font-size: 1rem;\n  font-weight: 500;\n}\n\np.small {\n  font-size: 0.85rem;\n}\n\n/* for firefox */\n* {\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-primary-700) rgba(0, 0, 0, 0);\n}\n\n/* for non-firefox */\n::-webkit-scrollbar {\n  overflow: overlay;\n  width: 8px;\n  /* shouldnt hardcode width, pls find way to make it dynamic, thin wasn't working for me...*/\n}\n\n::-webkit-scrollbar-track {\n  display: initial;\n}\n\n::-webkit-scrollbar-thumb {\n  background-color: var(--color-primary-700);\n  border-radius: 5px;\n}\n\nhtml,\nbody,\n#__next {\n  background-color: var(--color-primary-900);\n  height: 100%;\n  width: 100%;\n  display: flex;\n}\n\naudio {\n  width: 0;\n  display: none !important;\n}\n\nhtml {\n  position: fixed;\n}\n\n#__next {\n  overflow-y: auto;\n}\n\n::-webkit-resizer {\n  background: var(--color-primary-700);\n}\n\n#nprogress {\n  position: relative;\n  z-index: 9999999;\n}\n\n#nprogress .bar {\n  background: var(--color-accent-hover) !important;\n}\n\n#nprogress .peg {\n  box-shadow: 0 0 10px var(--color-accent-hover),\n    0 0 5px var(--color-accent-hover) !important;\n}\n\n#nprogress .spinner-icon {\n  border-top-color: var(--color-accent-hover) !important;\n  border-left-color: var(--color-accent-hover) !important;\n}\n\nimg.emoji {\n  height: 1.2rem;\n  width: 1.2rem;\n  margin: 0 0.05em 0 0.1em;\n  top: -0.1em;\n  vertical-align: middle;\n  display: inline-block;\n  position: relative;\n  -webkit-user-drag: none;\n  -moz-user-drag: none;\n  -o-user-drag: none;\n  user-drag: none;\n  cursor: text;\n}\n\n.h-screen {\n  height: calc(100vh - var(--screen-height-reduction));\n}\n\nbutton:focus {\n  outline: none;\n}\n"
  },
  {
    "path": "kibbeh/src/types/PageComponent.ts",
    "content": "import { NextPage } from \"next\";\n\nexport type PageComponent<T> = NextPage<T> & { ws?: boolean };\n"
  },
  {
    "path": "kibbeh/src/types/index.d.ts",
    "content": "declare module \"*.png\";\n"
  },
  {
    "path": "kibbeh/src/types/overrides-mui.d.ts",
    "content": "import { Overrides } from \"@material-ui/core/styles/overrides\";\nimport { MuiPickersOverrides } from \"@material-ui/pickers/typings/overrides\";\n\ntype overridesNameToClassKey = {\n  [P in keyof MuiPickersOverrides]: keyof MuiPickersOverrides[P];\n};\n\ndeclare module \"@material-ui/core/styles/overrides\" {\n  export interface ComponentNameToClassKey extends overridesNameToClassKey {}\n}\n"
  },
  {
    "path": "kibbeh/src/types/user.d.ts",
    "content": ""
  },
  {
    "path": "kibbeh/src/types/util-types.ts",
    "content": "export type Await<T> = T extends Promise<infer U> ? U : T;\n\ntype Join<S1, S2> = S1 extends string\n  ? S2 extends string\n    ? `${S1}.${S2}`\n    : never\n  : never;\n\nexport type Paths<T> = {\n  [K in keyof T]: T[K] extends Record<string, unknown>\n    ? Join<K, Paths<T[K]>>\n    : K;\n}[keyof T];\n"
  },
  {
    "path": "kibbeh/src/types/wakeLock.d.ts",
    "content": "interface BaseWebLockSentinelEventMap {\n  onrelease: Event;\n}\n\ntype WakeLockSentinel = {\n  readonly released: boolean;\n  readonly type: string;\n  release(): Promise;\n  addEventListener(\n    type: K,\n    listener: (this: Event, ev: BaseWebLockSentinelEventMap[K]) => any,\n    options?: boolean | AddEventListenerOptions\n  ): void;\n  addEventListener(\n    type: string,\n    listener: EventListenerOrEventListenerObject,\n    options?: boolean | AddEventListenerOptions\n  ): void;\n  removeEventListener(\n    type: K,\n    listener: (this: Event, ev: BaseWebLockSentinelEventMap[K]) => any,\n    options?: boolean | EventListenerOptions\n  ): void;\n  removeEventListener(\n    type: string,\n    listener: EventListenerOrEventListenerObject,\n    options?: boolean | EventListenerOptions\n  ): void;\n};\n\ntype WakeLockRequestType = \"screen\";\n\ninterface Navigator {\n  wakeLock: {\n    request: (type: WakeLockRequestType) => Promise;\n  };\n}\n"
  },
  {
    "path": "kibbeh/src/ui/Banner.tsx",
    "content": "import * as React from \"react\";\nimport { SolidPlus } from \"../icons\";\n\nexport type BannerDurations = \"default\" | \"sticky\";\n\nexport interface BannerProps {\n  message: string;\n  button?: React.ReactNode;\n  duration?: BannerDurations;\n  onClose?: () => void;\n}\n\nexport const Banner: React.FC<BannerProps> = ({\n  message,\n  button,\n  duration = \"default\",\n  onClose,\n}) => {\n  const onCloseRef = React.useRef(onClose);\n  onCloseRef.current = onClose;\n  React.useEffect(() => {\n    if (duration === \"sticky\") {\n      return;\n    }\n\n    const timer = setTimeout(() => {\n      onCloseRef.current?.();\n    }, 7000);\n\n    return () => {\n      clearTimeout(timer);\n    };\n  }, [duration]);\n\n  return (\n    <div\n      className={`flex rounded-8 p-3 relative w-full items-center justify-center text-button transition-transform duration-300 bg-secondary`}\n      data-testid=\"banner-message\"\n    >\n      {onClose ? (\n        <div\n          className={`flex absolute cursor-pointer`}\n          style={{\n            top: 5,\n            right: 7,\n            width: 13,\n            height: 13,\n          }}\n          onClick={onClose}\n          data-testid=\"close-btn\"\n        >\n          <SolidPlus style={{ transform: \"rotate(45deg)\" }} />\n        </div>\n      ) : null}\n      <div className={`flex space-x-4 items-center`}>\n        <div className={`bold`}>{message}</div>\n        {button}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/BannerButton.tsx",
    "content": "import React, {\n  ButtonHTMLAttributes,\n  DetailedHTMLProps,\n  ReactNode,\n} from \"react\";\nimport { Spinner } from \"./Spinner\";\n\nconst colorClassnames = {\n  primary:\n    \"text-button bg-accent hover:bg-accent-hover disabled:text-accent-disabled disabled:bg-accent-hover\",\n  secondary:\n    \"text-button bg-primary-700 hover:bg-primary-600 disabled:text-primary-300\",\n  \"secondary-800\":\n    \"text-button bg-primary-800 hover:bg-primary-600 disabled:text-primary-300\",\n};\n\nexport type ButtonProps = DetailedHTMLProps<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  HTMLButtonElement\n> & {\n  loading?: boolean;\n  icon?: ReactNode;\n};\n\nexport const BannerButton: React.FC<ButtonProps> = ({\n  children,\n  icon,\n  className = \"\",\n  ...props\n}) => {\n  return (\n    <button\n      className={`flex outline-none focus:ring-4 px-2 py-1 text-sm rounded-md \n      transition duration-200 ease-in-out\n      text-button bg-transparent font-bold items-center justify-center ${className} banner-btn`}\n      data-testid=\"button\"\n      {...props}\n    >\n      <span className={`flex items-center`}>\n        {icon ? <span className={`mr-2 items-center`}>{icon}</span> : null}\n        {children}\n      </span>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/BaseDropdownSm.tsx",
    "content": "import React from \"react\";\n\ntype BaseDropdownSmItemProps = React.DetailedHTMLProps<\n  React.HTMLAttributes<HTMLDivElement>,\n  HTMLDivElement\n> & {\n  children: React.ReactNode;\n};\n\nexport const BaseDropdownSmItem: React.FC<BaseDropdownSmItemProps> = ({\n  children,\n  className,\n  ...props\n}) => (\n  <div\n    className={`px-3 py-1 cursor-pointer hover:bg-primary-700 ${className}`}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\ntype BaseDropdownSmProps = React.DetailedHTMLProps<\n  React.HTMLAttributes<HTMLDivElement>,\n  HTMLDivElement\n> & {\n  children: React.ReactNodeArray;\n};\n\nexport const BaseDropdownSm: React.FC<BaseDropdownSmProps> = ({\n  children,\n  className,\n  ...props\n}) => (\n  <div\n    className={`text-primary-100 bg-primary-800\n    border border-primary-700 rounded-md\n    py-2 shadow-lg inline-flex flex-col gap-y-1 ${className}`}\n    {...props}\n  >\n    {children}\n  </div>\n);\n"
  },
  {
    "path": "kibbeh/src/ui/BaseOverlay.tsx",
    "content": "import React, { MouseEventHandler, ReactNode } from \"react\";\nimport ProfileHeaderWrapperStory from \"../stories/ProfileHeaderWrapper.story\";\n\nexport interface BaseOverlayProps\n  extends React.ComponentPropsWithoutRef<\"div\"> {\n  title?: string;\n  actionButton?: string;\n  onActionButtonClicked?: MouseEventHandler<HTMLButtonElement>;\n  children: ReactNode;\n  overlay?: ReactNode;\n}\n\nexport const BaseOverlay: React.FC<BaseOverlayProps> = ({\n  children,\n  title,\n  actionButton,\n  overlay,\n  onActionButtonClicked,\n  ...props\n}) => {\n  return (\n    <div\n      className=\"flex flex-col w-full rounded-8 bg-primary-800 border border-primary-700 overflow-hidden relative\"\n      data-testid=\"base-overlay\"\n      {...props}\n    >\n      {overlay ? overlay : \"\"}\n      {title && (\n        <div className=\"px-4 py-3 border-b border-primary-600 flex items-center\">\n          <h4 className=\"text-primary-100\">{title}</h4>\n        </div>\n      )}\n\n      <div className=\"flex flex-col text-primary-100\">{children}</div>\n\n      {actionButton && (\n        <button\n          className=\"flex px-4 bg-primary-700 text-primary-100 outline-none font-bold\"\n          style={{\n            paddingTop: 8,\n            paddingBottom: 12,\n            borderRadius: \"0 0 8px 8px\",\n          }}\n          onClick={onActionButtonClicked}\n        >\n          {actionButton}\n        </button>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/BaseSettingsItem.tsx",
    "content": "import React, { ReactNode } from \"react\";\n\nexport interface BaseSettingsItemProps {\n  children: ReactNode;\n  className: string;\n}\n\nexport const BaseSettingsItem: React.FC<BaseSettingsItemProps> = ({\n  children,\n  className = \"\",\n  ...props\n}) => {\n  return (\n    <div className={`bg-primary-900 rounded-8 ${className}`} {...props}>\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/BoxedIcon.tsx",
    "content": "import React, { ReactElement } from \"react\";\n\nconst colorMap = {\n  \"700\": \"bg-primary-700\",\n  \"800\": \"bg-primary-800\",\n};\n\nexport interface BoxedIconProps\n  extends React.ComponentPropsWithoutRef<\"button\"> {\n  circle?: boolean;\n  transition?: boolean;\n  hover?: boolean;\n  color?: keyof typeof colorMap;\n}\n\nexport const BoxedIcon: React.FC<BoxedIconProps> = ({\n  color = \"700\",\n  children,\n  className = \"\",\n  circle = false,\n  transition = false,\n  hover = false,\n  ...props\n}) => {\n  return (\n    <button\n      className={`flex ${colorMap[color]} ${\n        transition ? `transition duration-200 ease-in-out` : ``\n      } ${\n        hover ? `` : `hover:bg-primary-600`\n      } h-6 w-6 cursor-pointer justify-center items-center ${\n        circle ? `rounded-full` : `rounded-8`\n      } ${className.includes(\"text-button\") ? \"\" : \"text-primary-100\"}\n        ${className}`}\n      data-testid=\"boxed-icon\"\n      {...props}\n    >\n      {children}\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/BubbleText.tsx",
    "content": "import * as React from \"react\";\n\nexport interface BubbleTextProps {\n  live?: boolean;\n  children: React.ReactNode;\n}\n\nexport const BubbleText: React.FC<BubbleTextProps> = ({ live, children }) => {\n  return (\n    <div\n      className=\"text-primary-200 font-bold items-center\"\n      data-testid=\"bubble-text\"\n    >\n      <div\n        className={`inline-block mr-2 w-2 h-2 rounded-full ${\n          live ? \"bg-accent\" : \"bg-primary-300\"\n        }`}\n      ></div>\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Button.tsx",
    "content": "import React, {\n  ButtonHTMLAttributes,\n  DetailedHTMLProps,\n  ReactNode,\n} from \"react\";\nimport { Spinner } from \"./Spinner\";\n\nconst sizeClassnames = {\n  big: \"py-2 px-6 text-sm rounded-lg\",\n  small: \"px-2 py-1 text-sm rounded-md\",\n  tiny: \"px-1 text-sm rounded-5\",\n};\n\nconst colorClassnames = {\n  primary:\n    \"text-button bg-accent transition duration-200 ease-in-out hover:bg-accent-hover disabled:text-accent-disabled disabled:bg-accent-hover\",\n  secondary:\n    \"text-button bg-primary-700 hover:bg-primary-600 disabled:text-primary-300\",\n  \"secondary-800\":\n    \"text-button bg-primary-800 hover:bg-primary-600 disabled:text-primary-300\",\n  \"primary-300\":\n    \"text-button bg-primary-700 hover:bg-primary-600 disabled:text-primary-300\",\n  transparent: \"text-button bg-transparent\",\n  \"accent-secondary\":\n    \"text-button bg-secondary hover:bg-secondary-washed-out disabled:text-secondary-washed-out\",\n};\n\nexport type ButtonProps = DetailedHTMLProps<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  HTMLButtonElement\n> & {\n  size?: keyof typeof sizeClassnames;\n  color?: keyof typeof colorClassnames;\n  loading?: boolean;\n  icon?: ReactNode;\n  transition?: boolean;\n};\n\nexport const Button: React.FC<ButtonProps> = ({\n  children,\n  size = \"big\",\n  color = \"primary\",\n  disabled,\n  loading,\n  icon,\n  className = \"\",\n  transition,\n  ...props\n}) => {\n  return (\n    <button\n      disabled={disabled || loading}\n      className={`flex outline-none focus:ring-4 focus:ring-${color} ${sizeClassnames[size]\n        } ${transition ? `transition duration-200 ease-in-out` : ``} ${colorClassnames[color]\n        } font-bold flex items-center justify-center ${className}`}\n      data-testid=\"button\"\n      {...props}\n    >\n      <span className={loading ? \"opacity-0\" : `flex items-center`}>\n        {icon ? <span className={`mr-2 items-center`}>{icon}</span> : null}\n        {children}\n      </span>\n      {loading ? (\n        <span className={`absolute`}>\n          <Spinner size={size === \"small\" ? \"2\" : \"4\"} />\n        </span>\n      ) : null}\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ButtonLink.tsx",
    "content": "import React from \"react\";\n\nexport const ButtonLink: React.FC<React.ComponentPropsWithoutRef<\"button\">> = ({\n  children,\n  className,\n  ...props\n}) => {\n  return (\n    <button\n      className={`text-primary-100 underline text-md ${className}`}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/CenterLoader.tsx",
    "content": "import React from \"react\";\nimport { Spinner } from \"./Spinner\";\n\ninterface CenterLoaderProps {}\n\nexport const CenterLoader: React.FC<CenterLoaderProps> = ({}) => {\n  return (\n    <div className={`flex w-full h-full items-center justify-center`}>\n      <Spinner />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ChangeAvatarCard.tsx",
    "content": "import React, { useState } from \"react\";\nimport { BaseSettingsItem } from \"./BaseSettingsItem\";\nimport { SingleUser } from \"./UserAvatar\";\nimport { Button } from \"./Button\";\nimport SolidTrashIcon from \"../icons/SolidTrash\";\n\nexport interface ChangeAvatarCardProps {\n  avatarUrl: string;\n}\n\nexport const ChangeAvatarCard: React.FC<ChangeAvatarCardProps> = ({\n  avatarUrl,\n}) => {\n  const [baseAvatarUrl, setAvatarUrl] = useState(avatarUrl);\n\n  const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const MAX_FILE_SIZE = 3145728;\n    const file = (e.target.files as FileList)[0];\n\n    if (file.size > MAX_FILE_SIZE) return; // show error toast\n\n    if (file.size < 1) return;\n\n    // add upload to image store/github/twitter\n    setAvatarUrl(URL.createObjectURL(file));\n  };\n\n  const handleImageDelete = () => {\n    // handle delete and change image to default and setAvatarUrl to default avatar\n    setAvatarUrl(\"\");\n  };\n\n  return (\n    <BaseSettingsItem className=\"flex items-center px-4 py-3\">\n      <div>\n        <SingleUser src={baseAvatarUrl} />\n      </div>\n      <div className=\"flex flex-col ml-5\">\n        <div className=\"flex items-center\">\n          <Button size=\"small\" color=\"secondary\" className=\"\">\n            <label\n              htmlFor=\"avatar\"\n              className=\"relative cursor-pointer text-primary-100\"\n            >\n              Change profile picture\n              <input\n                onChange={handleImageUpload}\n                type=\"file\"\n                name=\"avatar\"\n                id=\"avatar\"\n                accept=\"image/png, image/jpeg\"\n                className=\"hidden\"\n              />\n            </label>\n          </Button>\n          <Button\n            size=\"small\"\n            color=\"secondary\"\n            className=\"ml-2\"\n            style={{ minHeight: \"28px\" }}\n            onClick={handleImageDelete}\n          >\n            <SolidTrashIcon className=\"text-primary-100\" />\n          </Button>\n        </div>\n        <div className=\"mt-2\">\n          <div className=\"text-primary-300 text-sm\">\n            Only JPG or PNG and maximum 3MB.\n          </div>\n        </div>\n      </div>\n    </BaseSettingsItem>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ChangeBannerCard.tsx",
    "content": "import React, { useState, useRef, ChangeEventHandler } from \"react\";\n\nimport { Button } from \"./Button\";\nimport { SolidTrash } from \"../icons\";\n\nexport interface ChangeBannerCardProps {\n  bannerUrl: string;\n}\n\nexport const ChangeBannerCard: React.FC<ChangeBannerCardProps> = ({\n  bannerUrl,\n}) => {\n  const [banner, setBanner] = useState(bannerUrl);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const deleteHandler = () => {\n    console.log(\"banner deleted\");\n  };\n\n  const fileChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {\n    if (e.target.files!.length < 1) return;\n    const file = e.target.files![0];\n    if (file.size > 5000000) {\n      console.log(\"File size must be less than 5MB\");\n      return;\n    }\n    const type = file.type.toLocaleLowerCase();\n    if (type.includes(\"jpg\") || type.includes(\"jpeg\") || type.includes(\"png\")) {\n      setBanner(URL.createObjectURL(file));\n      // upload the new banner\n    }\n  };\n\n  const changeProfileButtonClickHandler = () => {\n    if (!fileInputRef.current) return;\n    fileInputRef.current.click();\n  };\n\n  return (\n    <div className=\"rounded-8 flex flex-col bg-primary-900\">\n      <img\n        src={banner}\n        alt=\"banner\"\n        style={{ maxHeight: \"145px\" }}\n        className=\"w-full h-full rounded-t-8 object-cover\"\n      />\n      <div className=\"flex justify-between items-center p-4\">\n        <div className=\"flex\">\n          <Button\n            size=\"small\"\n            color=\"primary-300\"\n            onClick={changeProfileButtonClickHandler}\n          >\n            Change profile header\n          </Button>\n          <Button\n            className=\"ml-2\"\n            color=\"primary-300\"\n            size=\"small\"\n            onClick={deleteHandler}\n          >\n            <SolidTrash />\n          </Button>\n          <input\n            type=\"file\"\n            ref={fileInputRef}\n            onChange={fileChangeHandler}\n            className=\"w-0 h-0 invisible\"\n            accept=\"image/jpeg, image/jpg, image/png\"\n          />\n        </div>\n        <span className=\"text-primary-300 font-medium text-sm\">\n          Only JPG or PNG and maximum 5MB.\n        </span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/DropdownController.tsx",
    "content": "import React, { useState, useEffect, useRef } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { usePopper } from \"react-popper\";\n\nexport const DropdownController: React.FC<{\n  portal?: boolean;\n  className?: string;\n  innerClassName?: string;\n  overlay: (c: () => void) => React.ReactNode;\n  zIndex?: number;\n}> = ({\n  children,\n  className,\n  innerClassName,\n  overlay,\n  portal = true,\n  zIndex,\n}) => {\n  const [visible, setVisibility] = useState(false);\n\n  const referenceRef = useRef<HTMLButtonElement>(null);\n  const popperRef = useRef<HTMLDivElement>(null);\n\n  const { styles, attributes } = usePopper(\n    referenceRef.current,\n    popperRef.current,\n    {\n      modifiers: [{ name: \"eventListeners\", enabled: visible }],\n      placement: \"left\",\n    }\n  );\n\n  useEffect(() => {\n    const handleDocumentClick = (event: any) => {\n      if (\n        referenceRef.current?.contains(event.target) ||\n        popperRef.current?.contains(event.target)\n      ) {\n        return;\n      }\n      setVisibility(false);\n    };\n    // listen for clicks and close dropdown on body\n    document.addEventListener(\"mousedown\", handleDocumentClick);\n    return () => {\n      document.removeEventListener(\"mousedown\", handleDocumentClick);\n    };\n  }, []);\n\n  const body = (\n    <div\n      className={`absolute ${className}`}\n      ref={popperRef}\n      {...attributes.popper}\n      style={{ zIndex: zIndex || 5 }}\n    >\n      <div\n        style={styles.offset}\n        className={`${visible ? \"\" : \"hidden\"} ${innerClassName}`}\n      >\n        {visible ? overlay(() => setVisibility(false)) : null}\n      </div>\n    </div>\n  );\n\n  return (\n    <React.Fragment>\n      <button\n        className=\"flex focus:outline-no-chrome\"\n        ref={referenceRef}\n        onClick={() => setVisibility(!visible)}\n        data-testid=\"dropdown-trigger\"\n      >\n        {children}\n      </button>\n      {portal ? createPortal(body, document.querySelector(\"#main\")!) : body}\n    </React.Fragment>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/DurationTicker.tsx",
    "content": "import { differenceInSeconds } from \"date-fns\";\nimport React, { useEffect, useState } from \"react\";\n\ninterface DurationTickerProps {\n  dt: Date;\n}\n\nconst pad = (s: number) => (s < 10 ? `0${s}` : \"\" + s);\n\nexport const DurationTicker: React.FC<DurationTickerProps> = ({ dt }) => {\n  const [_, rerender] = useState(0);\n  useEffect(() => {\n    const id = setInterval(() => {\n      rerender((c) => c + 1);\n    }, 1000);\n    return () => {\n      clearInterval(id);\n    };\n  }, []);\n\n  let totalSeconds = differenceInSeconds(new Date(), dt);\n\n  const parts: string[] = [];\n  const hours = Math.floor(totalSeconds / 3600);\n  totalSeconds %= 3600;\n  const minutes = Math.floor(totalSeconds / 60);\n  const seconds = totalSeconds % 60;\n\n  if (hours > 0) {\n    parts.push(pad(hours));\n  }\n  parts.push(pad(minutes));\n  parts.push(pad(seconds));\n\n  return <>{parts.join(\":\")}</>;\n};\n"
  },
  {
    "path": "kibbeh/src/ui/EmojiPicker.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { useEmojiPickerStore } from \"../global-stores/useEmojiPickerStore\";\nimport { CustomEmote } from \"../modules/room/chat/EmoteData\";\nimport { useRoomChatStore } from \"../modules/room/chat/useRoomChatStore\";\ninterface EmojiPickerProps {\n  emojiSet: CustomEmote[];\n  onEmojiSelect: (emoji: CustomEmote) => void;\n}\n\nexport const EmojiPicker: React.FC<EmojiPickerProps> = ({\n  emojiSet,\n  onEmojiSelect,\n}) => {\n  const { message } = useRoomChatStore();\n  const {\n    open,\n    setOpen,\n    setQueryMatches,\n    queryMatches,\n    keyboardHoveredEmoji,\n    setKeyboardHoveredEmoji,\n    setQuery,\n  } = useEmojiPickerStore();\n\n  // Open picker on colon syntax\n  useEffect(() => {\n    // colon syntax regex\n    const colonSyntaxMatches = message.match(/^(?!.*\\bRT\\b)(?:.+\\s)?:\\w+$/i);\n\n    if (colonSyntaxMatches) {\n      const emojiQueries = colonSyntaxMatches[0].split(\" \");\n      const query = emojiQueries[emojiQueries.length - 1].toLowerCase();\n      const queryMatchesE = emojiSet\n        .filter(\n          (e) =>\n            e.keywords\n              .map((k) => k.toLowerCase())\n              .filter((k) => k.includes(query.replace(\":\", \"\"))).length\n        )\n        .slice(0, 7);\n\n      setQueryMatches(queryMatchesE);\n      setKeyboardHoveredEmoji(\n        queryMatchesE.length ? queryMatchesE[0].name : null\n      );\n      setOpen(!!queryMatchesE.length);\n      setQuery(query);\n    } else {\n      // Close picker if no matches\n      setQueryMatches([]);\n      setOpen(false);\n    }\n  }, [\n    emojiSet,\n    message,\n    setKeyboardHoveredEmoji,\n    setOpen,\n    setQuery,\n    setQueryMatches,\n  ]);\n\n  if (!open) return null;\n\n  return (\n    <div\n      className={`flex bg-primary-700 rounded-8 flex flex-row flex-grow p-1 max-h-24 pt-2 px-2 absolute bottom-full w-full`}\n      data-testid=\"emote-picker\"\n    >\n      <div\n        className={`flex grid grid-cols-7 w-full pr-3 gap-2 max-h-16 overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-xl scrollbar-thumb-primary-800`}\n      >\n        {(queryMatches.length ? queryMatches : emojiSet).map((emoji) => (\n          <img\n            key={emoji.name}\n            src={emoji.imageUrl}\n            className={`w-5 max-w-5 cursor-pointer ${\n              keyboardHoveredEmoji === emoji.name\n                ? \"bg-primary-300 rounded p-1\"\n                : \"\"\n            }`}\n            onClick={() => onEmojiSelect(emoji)}\n          />\n        ))}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ErrorButtonItem.tsx",
    "content": "import React from \"react\";\nimport { BaseSettingsItem } from \"./BaseSettingsItem\";\nimport { Button } from \"./Button\";\nimport { SolidWarning } from \"../icons\";\n\nexport interface ErrorButtonItemProps {\n  errorMessageHeading?: string;\n  errorMessageText: string;\n  actionButtonText?: string;\n}\n\nexport const ErrorButtonItem: React.FC<ErrorButtonItemProps> = ({\n  errorMessageHeading,\n  errorMessageText,\n  actionButtonText,\n}) => {\n  return (\n    <BaseSettingsItem className=\"flex items-center justify-between px-3 py-2 w-full\">\n      <div>\n        <SolidWarning className=\"text-secondary w-4 h-4\" />\n      </div>\n      <div className=\"flex flex-col pl-3 pr-4 flex-auto\">\n        {!!errorMessageHeading && (\n          <div className=\"font-bold text-secondary\">{errorMessageHeading}</div>\n        )}\n        <div className=\"text-primary-300\">{errorMessageText}</div>\n      </div>\n      <div>\n        {!!actionButtonText && (\n          <Button size=\"small\" color=\"accent-secondary\" className=\"px-3\">\n            {actionButtonText}\n          </Button>\n        )}\n      </div>\n    </BaseSettingsItem>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ErrorMessageButton.tsx",
    "content": "import React from \"react\";\n\nexport const ErrorMessageButton: React.FC<\n  React.ComponentPropsWithoutRef<\"button\">\n> = ({ children, className, style, ...props }) => {\n  return (\n    <button\n      className={`rounded-lg px-3 font-bold text-sm bg-secondary-washed-out ${className}`}\n      data-testid=\"error-msg-btn\"\n      style={{\n        paddingTop: 3,\n        paddingBottom: 3,\n        ...style,\n      }}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ErrorToast.tsx",
    "content": "import * as React from \"react\";\nimport { SolidPlus } from \"../icons\";\n\nexport type ToastDurations = \"default\" | \"sticky\";\n\nexport interface ErrorMessageProps {\n  message: string;\n  button?: React.ReactNode;\n  duration?: ToastDurations;\n  onClose?: () => void;\n}\n\nexport const ErrorToast: React.FC<ErrorMessageProps> = ({\n  message,\n  button,\n  duration = \"default\",\n  onClose,\n}) => {\n  const onCloseRef = React.useRef(onClose);\n  onCloseRef.current = onClose;\n  React.useEffect(() => {\n    if (duration === \"sticky\") {\n      return;\n    }\n\n    const timer = setTimeout(() => {\n      onCloseRef.current?.();\n    }, 7000);\n\n    return () => {\n      clearTimeout(timer);\n    };\n  }, [duration]);\n\n  return (\n    <div\n      className={`flex rounded-8 p-3 relative w-full items-center justify-center text-button transition-transform duration-300 bg-secondary`}\n      data-testid=\"error-message\"\n    >\n      {onClose ? (\n        <div\n          className={`flex absolute cursor-pointer`}\n          style={{\n            top: 5,\n            right: 7,\n            width: 13,\n            height: 13,\n          }}\n          onClick={onClose}\n          data-testid=\"close-btn\"\n        >\n          <SolidPlus style={{ transform: \"rotate(45deg)\" }} />\n        </div>\n      ) : null}\n      <div className={`flex space-x-4 items-center`}>\n        <div className={`bold`}>{message}</div>\n        {button}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/FeedHeader.tsx",
    "content": "import { Room, ScheduledRoom } from \"@dogehouse/kebab\";\nimport React, { FC, MouseEventHandler, ReactNode } from \"react\";\nimport { Button } from \"./Button\";\n\nexport interface FeedProps {\n  rooms: Array<Room | ScheduledRoom>;\n  emptyPlaceholder: ReactNode;\n  onRoomClick?: (room: Room | ScheduledRoom) => void;\n}\n\nexport interface FeedHeaderProps {\n  title: string;\n  actionTitle: string;\n  onActionClicked: MouseEventHandler<HTMLButtonElement>;\n}\n\nexport const FeedHeader: FC<FeedHeaderProps> = ({\n  actionTitle,\n  onActionClicked,\n  title,\n}) => {\n  return (\n    <div className=\"flex justify-between items-end mb-5 ml-4\">\n      <h4 className=\"text-primary-100\">{title}</h4>\n      <Button\n        data-testid=\"feed-action-button\"\n        transition\n        onClick={onActionClicked}\n      >\n        {actionTitle}\n      </Button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/FollowersOnline.tsx",
    "content": "import { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport Link from \"next/link\";\nimport React, { MouseEventHandler } from \"react\";\nimport { ApiPreloadLink } from \"../shared-components/ApiPreloadLink\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { SingleUser } from \"./UserAvatar/SingleUser\";\n\nexport interface FriendOnlineType {\n  username: string;\n  avatarUrl: string;\n  isOnline: boolean;\n  activeRoom?: {\n    name: string;\n    link?: string;\n  };\n}\n\nexport interface FriendsOnlineProps {\n  onlineFriendList: UserWithFollowInfo[];\n  onlineFriendCount?: number;\n  showMoreAction?: MouseEventHandler<HTMLDivElement>;\n}\n\nexport const FollowerOnline: React.FC<UserWithFollowInfo> = ({\n  username,\n  avatarUrl: avatar,\n  online,\n  currentRoom,\n}) => (\n  <div className=\"flex py-3 w-full\">\n    <ApiPreloadLink route=\"profile\" data={{ username }}>\n      <SingleUser\n        size=\"sm\"\n        isOnline={online}\n        src={avatar}\n        username={username}\n      />\n    </ApiPreloadLink>\n    <div className=\"flex ml-3 flex-col overflow-hidden justify-center\">\n      <ApiPreloadLink route=\"profile\" data={{ username }}>\n        <h5 className=\"text-primary-100 font-bold\">{username}</h5>\n      </ApiPreloadLink>\n      {currentRoom ? (\n        <Link href={`/room/[id]`} as={`/room/${currentRoom.id}`}>\n          <a className={`hover:underline text-primary-300 truncate block`}>\n            {currentRoom.name}\n          </a>\n        </Link>\n      ) : null}\n    </div>\n  </div>\n);\n\nexport const FollowersOnlineWrapper: React.FC<{\n  onlineFriendCount?: number;\n}> = ({ onlineFriendCount, children }) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <div\n      className=\"pb-5 w-full flex flex-col flex-1 overflow-y-auto\"\n      data-testid=\"friends-online\"\n    >\n      <h4 className=\"text-primary-100\">\n        {t(\"components.followingOnline.people\")}\n      </h4>\n      <h6 className=\"text-primary-300 mt-3 text-sm font-bold uppercase\">\n        {t(\"components.followingOnline.online\")}{\" \"}\n        {onlineFriendCount !== undefined ? `(${onlineFriendCount})` : null}\n      </h6>\n      <div className=\"flex flex-col mt-3 overflow-y-auto scrollbar-thin scrollbar-thumb-primary-700 overflow-x-hidden\">\n        {children}\n      </div>\n    </div>\n  );\n};\n\nexport const FollowersOnlineShowMore: React.FC<{ onClick?: () => void }> = ({\n  onClick,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <button\n      className=\"underline text-primary-300 font-bold mt-4 cursor-pointer\"\n      onClick={onClick}\n      data-testid=\"show-more-btn\"\n    >\n      {t(\"components.followingOnline.showMore\")}\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/GridPanel.tsx",
    "content": "import React from \"react\";\n\nexport const GridPanel: React.FC = ({ children }) => {\n  return <div className={`flex flex-col flex-1 w-full`}>{children}</div>;\n};\n\nexport const FixedGridPanel: React.FC = ({ children }) => {\n  return (\n    <div className={`flex pt-5 flex-col flex-1 sticky top-0 h-screen`}>\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/InfoText.tsx",
    "content": "import React from \"react\";\n\ninterface InfoTextProps {\n  className?: string;\n}\n\nexport const InfoText: React.FC<InfoTextProps> = ({ className, children }) => {\n  return <div className={`text-primary-200 ${className}`}>{children}</div>;\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Input.tsx",
    "content": "/* eslint-disable no-unused-expressions */\nimport React, { forwardRef } from \"react\";\n\nexport interface InputProps extends React.ComponentPropsWithoutRef<\"input\"> {\n  textarea?: boolean;\n  rows?: number;\n  error?: string;\n  transparent?: boolean;\n}\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n  ({ className, textarea, error, transparent, ...props }, ref) => {\n    const bg = transparent ? `bg-transparent` : `bg-primary-700`;\n    const ring = error ? `ring-1 ring-secondary` : \"\";\n    const cn = `w-full py-2 px-4 rounded-8 text-primary-100 placeholder-primary-300 focus:outline-none ${bg} ${ring} ${className} `;\n\n    return textarea ? (\n      <textarea\n        ref={ref as any}\n        className={cn}\n        data-testid=\"textarea\"\n        {...(props as any)}\n      />\n    ) : (\n      <input ref={ref} className={cn} data-testid=\"input\" {...props} />\n    );\n  }\n);\n\nInput.displayName = \"Input\";\n"
  },
  {
    "path": "kibbeh/src/ui/InputErrorMsg.tsx",
    "content": "import React from \"react\";\n\ninterface InputErrorMsgProps {}\n\n// @todo verify with designer what color this should be\nexport const InputErrorMsg: React.FC<InputErrorMsgProps> = ({ children }) => {\n  return (\n    <div className={`flex text-secondary`} data-testid=\"input-error-msg\">\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/KeybindCard.tsx",
    "content": "import React from \"react\";\nimport { BaseSettingsItem } from \"./BaseSettingsItem\";\nimport { Button } from \"./Button\";\nimport SolidKeyboardIcon from \"../icons/SolidKeyboard\";\nimport { Tag } from \"./Tag\";\n\nconst modifiers = {\n  ctrl: \"ctrlkey\",\n  alt: \"altkey\",\n  shift: \"shiftkey\",\n};\nexport interface KeybindCardProps {\n  command: string;\n  modifier?: keyof typeof modifiers;\n  primaryKey: string;\n}\n\nexport const KeybindCard: React.FC<KeybindCardProps> = ({\n  command,\n  modifier,\n  primaryKey,\n}) => {\n  const [isListening, setIsListening] = React.useState(false);\n\n  React.useEffect(() => {\n    const handleKeybinding = (e: KeyboardEvent) => {\n      e.preventDefault();\n      // wait for key combination to avoid two updates\n      if ([\"Control\", \"Alt\", \"Shift\"].includes(e.key)) return;\n\n      let newModifier = \"\";\n\n      if (e.ctrlKey) newModifier = \"ctrl\";\n      if (e.altKey) newModifier = \"alt\";\n      if (e.shiftKey) newModifier = \"shift\";\n\n      const keybind = modifier ? [newModifier, e.key] : e.key;\n\n      // handle update keybind\n      // ...\n\n      console.log(\"keybind: \", keybind);\n      document.removeEventListener(\"keydown\", handleKeybinding);\n      setIsListening(false);\n    };\n\n    document.addEventListener(\"keydown\", handleKeybinding);\n\n    return () => {\n      document.removeEventListener(\"keydown\", handleKeybinding);\n    };\n  }, [isListening, modifier]);\n\n  return (\n    <BaseSettingsItem\n      className={`\n            flex justify-between items-center bg-primary-900 px-3 py-4 border border-transparent\n            transition\n            ${isListening && \"border-accent\"}\n         `}\n    >\n      <div>\n        <div className=\"text-base font-bold text-primary-100 capitalize\">\n          {command}\n        </div>\n      </div>\n      <div className=\"flex items-center\">\n        {isListening ? (\n          <Tag className=\"uppercase\">Listening...</Tag>\n        ) : (\n          <div className=\"space-x-1 flex\">\n            <Tag className=\"uppercase\">{modifier}</Tag>\n            <Tag className=\"uppercase\">{primaryKey}</Tag>\n          </div>\n        )}\n        <Button\n          size=\"tiny\"\n          color=\"transparent\"\n          className=\"ml-4\"\n          onClick={() => setIsListening(!isListening)}\n        >\n          <SolidKeyboardIcon className=\"text-primary-100\" />\n        </Button>\n      </div>\n    </BaseSettingsItem>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/LanguageSearch.tsx",
    "content": "import React from \"react\";\n\nexport const LanguageSearch: React.FC<{\n  onChange: (value: string) => void;\n}> = ({ onChange }: { onChange: (value: string) => void }) => {\n  const classes = `\n  flex w-full items-center px-4 py-4 md:py-2 md:border-none border-primary-700 text-primary-100 focus:outline-no-chrome whitespace-nowrap overflow-ellipsis bg-primary-900\n  `;\n  return (\n    <input\n      className={classes}\n      onChange={(v) => onChange(v.target.value)}\n      style={{ borderBottom: \"1px solid var(--color-primary-700)\" }}\n      placeholder=\"Search\"\n      autoFocus\n    />\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/LanguageSelector.tsx",
    "content": "import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ParseTextToTwemoji } from \"./Twemoji\";\nimport backIcon from \"../icons/SolidCaretRight\";\nimport { SettingsIcon } from \"./SettingsIcon\";\nimport { LanguageSearch } from \"./LanguageSearch\";\nimport { useRouter } from \"next/router\";\n\ninterface LanguageSelectorProps {\n  onClose?(): void;\n  mobile?: boolean;\n}\n\nexport const LanguageSelector: React.FC<LanguageSelectorProps> = ({\n  onClose,\n  mobile = false,\n}) => {\n  const languages = [\n    { value: \"en\", flag: \"🇬🇧\", label: \"English\" }, // English\n\n    /* Languages that are in ISO 639-1, sorted by language code (A-Z) */\n    { value: \"af\", flag: \"🇿🇦\", label: \"Afrikaans\" }, // Afrikaans\n    { value: \"am\", flag: \"🇪🇹\", label: \"አማርኛ\" }, // Amharic\n    { value: \"ar\", flag: \"🇸🇦\", label: \"عربي\" }, // Arabic\n    { value: \"az\", flag: \"🇦🇿\", label: \"Azərbaycanca\" }, // Azerbaijani\n    { value: \"bg\", flag: \"🇧🇬\", label: \"Български\" }, // Bulgarian\n    { value: \"bn\", flag: \"🇧🇩\", label: \"বাংলা\" }, // Bengali\n    { value: \"cs\", flag: \"🇨🇿\", label: \"Čeština\" }, // Czech\n    { value: \"da\", flag: \"🇩🇰\", label: \"Dansk\" }, // Danish\n    { value: \"de\", flag: \"🇩🇪\", label: \"Deutsch\" }, // German\n    { value: \"de-AT\", flag: \"🇦🇹\", label: \"Deutsch (Österreich)\" }, // German (Austria)\n    { value: \"gsw\", flag: \"🇨🇭\", label: \"Schwiizerdütsch\" }, // Swiss German\n    { value: \"el\", flag: \"🇬🇷\", label: \"Ελληνικά\" }, // Greek\n    { value: \"eo\", flag: \"🟢\", label: \"Esperanto\" }, // Esperanto\n    { value: \"es\", flag: \"🇪🇸\", label: \"Español\" }, // Spanish\n    { value: \"et\", flag: \"🇪🇪\", label: \"Eesti\" }, // Estonian\n    { value: \"eu\", flag: \"🇪🇸\", label: \"Euskara\" }, // Basque\n    { value: \"fa\", flag: \"🇮🇷\", label: \"فارسی\" }, // Persian\n    { value: \"fi\", flag: \"🇫🇮\", label: \"Suomi\" }, // Finnish\n    { value: \"fr\", flag: \"🇫🇷\", label: \"Français\" }, // French\n    { value: \"he\", flag: \"🇮🇱\", label: \"עברית\" }, // Hebrew\n    { value: \"hi\", flag: \"🇮🇳\", label: \"हिन्दी\" }, // Hindi\n    { value: \"hr\", flag: \"🇭🇷\", label: \"Hrvatski\" }, // Croatian\n    { value: \"hu\", flag: \"🇭🇺\", label: \"Magyar\" }, // Hungarian\n    { value: \"id\", flag: \"🇮🇩\", label: \"Bahasa Indonesia\" }, // Indonesian\n    { value: \"is\", flag: \"🇮🇸\", label: \"Íslenska\" }, // Icelandic\n    { value: \"it\", flag: \"🇮🇹\", label: \"Italiano\" }, // Italian\n    { value: \"ja\", flag: \"🇯🇵\", label: \"日本語\" }, // Japanese\n    { value: \"kk\", flag: \"🇰🇿\", label: \"Қазақша\" }, // Kazakh\n    { value: \"ko\", flag: \"🇰🇷\", label: \"한국어\" }, // Korean\n    { value: \"li\", flag: \"🇳🇱\", label: \"Limburgs\" }, // Limburgish\n    { value: \"lld\", flag: \"🐐\", label: \"Ladin\" }, // Ladin\n    { value: \"lt\", flag: \"🇱🇹\", label: \"Lietuvių\" }, // Lithuanian\n    { value: \"lv\", flag: \"🇱🇻\", label: \"Latviešu\" }, // Latvian\n    { value: \"nb\", flag: \"🇳🇴\", label: \"Norsk Bokmål\" }, // Norwegian Bokmål\n    { value: \"ne\", flag: \"🇳🇵\", label: \"नेपाली\" }, // Nepali\n    { value: \"nl\", flag: \"🇳🇱\", label: \"Nederlands\" }, // Dutch\n    { value: \"pl\", flag: \"🇵🇱\", label: \"Polski\" }, // Polish\n    { value: \"pt-BR\", flag: \"🇧🇷\", label: \"Português (Brasil)\" }, // Portuguese (Brazil)\n    { value: \"pt-PT\", flag: \"🇵🇹\", label: \"Português (Portugal)\" }, // Portuguese (Portugal)\n    { value: \"ro\", flag: \"🇷🇴\", label: \"Română\" }, // Romanian\n    { value: \"ru\", flag: \"🇷🇺\", label: \"Русский\" }, // Russian\n    { value: \"si\", flag: \"🇱🇰\", label: \"සිංහල\" }, // Sinhala\n    { value: \"sk\", flag: \"🇸🇰\", label: \"Slovenčina\" }, // Slovak\n    { value: \"sl\", flag: \"🇸🇮\", label: \"Slovenščina\" }, // Slovenian\n    { value: \"so\", flag: \"🇸🇴\", label: \"Af Soomaali\" }, // Somali\n    { value: \"sq\", flag: \"🇦🇱\", label: \"Shqip\" }, // Albanian\n    { value: \"sr\", flag: \"🇷🇸\", label: \"Српски\" }, // Serbian\n    { value: \"sr-LATIN\", flag: \"🇷🇸\", label: \"Srpski\" }, // Serbian (Latin)\n    { value: \"sv\", flag: \"🇸🇪\", label: \"Svenska\" }, // Swedish\n    { value: \"ta\", flag: \"🇮🇳\", label: \"தமிழ்\" }, // Tamil\n    { value: \"te\", flag: \"🇮🇳\", label: \"తెలుగు\" }, // Telugu\n    { value: \"th\", flag: \"🇹🇭\", label: \"ไทย\" }, // Thai\n    { value: \"tl\", flag: \"🇵🇭\", label: \"Tagalog\" }, // Tagalog\n    { value: \"tr\", flag: \"🇹🇷\", label: \"Türkçe\" }, // Turkish\n    { value: \"uk\", flag: \"🇺🇦\", label: \"Українська\" }, // Ukrainian\n    { value: \"ur\", flag: \"🇵🇰\", label: \"اردو\" }, // Urdu\n    { value: \"uz\", flag: \"🇺🇿\", label: \"Oʻzbek\" }, // Uzbek\n    { value: \"vi\", flag: \"🇻🇳\", label: \"Tiếng Việt\" }, // Vietnamese\n    { value: \"zh-CN\", flag: \"🇨🇳\", label: \"中文 (简体)\" }, // Chinese (Simplified)\n    { value: \"zh-TW\", flag: \"🇹🇼\", label: \"正體中文 (繁體)\" }, // Chinese (Traditional)\n  ].sort((a, b) => a.label.localeCompare(b.label));\n\n  const noveltyLanguages = [\n    /* Other languages */\n    { value: \"grc\", flag: \"🧓\", label: \"Αρχαία Ελληνικά\" }, // Ancient Greek\n    { value: \"en-C\", flag: \"🆕\", label: \"C'z Iŋglis̈\" }, // C's English\n    { value: \"en-LOLCAT\", flag: \"🐈\", label: \"LOLCAT\" },\n    { value: \"en-PIRATE\", flag: \"☠️\", label: \"Pirate\" },\n    { value: \"en-PIGLATIN\", flag: \"🐷\", label: \"Pig Latin\" },\n    { value: \"en-AU\", flag: \"🇦🇺\", label: \"uɐᴉꞁɐɹʇsnⱯ\" }, // Australian\n    { value: \"en-OWO\", flag: \"💕\", label: \"OwO Engwish\" },\n    { value: \"bottom\", flag: \"🥺\", label: \"Bottom\" },\n    { value: \"tp\", flag: \"💛\", label: \"Toki Pona\" },\n  ];\n\n  const options = [...languages, ...noveltyLanguages];\n\n  const { i18n } = useTranslation();\n  const { back } = useRouter();\n\n  const getOptions = (search: string) => {\n    return options.filter(v => v.label.toLowerCase().startsWith(search.toLowerCase())).map((e, i) => (\n      <SettingsIcon\n        key={e.value + i}\n        classes={`text-primary-100 focus:outline-no-chrome whitespace-nowrap overflow-ellipsis${\n          i18n.language === e.value ||\n          (e.value === \"en\" && i18n.language === \"en-US\")\n            ? \" bg-primary-700\"\n            : \"\"\n        }`}\n        onClick={() => {\n          i18n.changeLanguage(e.value);\n          if (mobile) {\n            back();\n          }\n        }}\n        last={i === options.length - 1}\n        icon={\n          <ParseTextToTwemoji text={e.label} style={{ marginRight: \"1ch\" }} />\n        }\n      ></SettingsIcon>\n    ));\n  };\n\n  const [parsedOptions, setParsedOptions] = React.useState(getOptions(''));\n\n  const parseOptions = (search: string) => {\n    setParsedOptions(getOptions(search));\n  };\n\n  return (\n    <div\n      className={`flex h-full w-full ${\n        mobile ? \"\" : \"z-20 absolute bg-primary-800\"\n      }`}\n    >\n      <div className=\"block h-full w-full\">\n        {mobile ? null : (\n          <div\n            className={`block h-6 w-full border-b border-primary-700 sticky top-0 bg-primary-800`}\n          >\n            {onClose ? (\n              <button\n                onClick={onClose}\n                className=\"absolute left-3 text-primary-100 top-1/2 transform translate-y-n1/2 py-1 focus:outline-no-chrome hover:bg-primary-700 z-30 rounded-5\"\n                style={{ paddingLeft: \"10px\", paddingRight: \"-6px\" }}\n              >\n                {backIcon({ style: { transform: \"rotate(180deg)\" } })}\n              </button>\n            ) : null}\n\n            <div className=\"block relative text-center top-1/2 transform translate-y-n1/2 w-full font-bold text-primary-100\">\n              Language\n            </div>\n          </div>\n        )}\n        <LanguageSearch onChange={v => parseOptions(v)}/>\n        <div\n          className=\"block h-full overflow-y-auto scrollbar-thin scrollbar-thumb-primary-700 overflow-x-hidden mb-9 md:pb-0\"\n          style={{ height: mobile ? \"auto\" : \"calc(100% - 40px)\" }}\n        >\n          <div className=\"block\">{parsedOptions}</div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/MacButton.tsx",
    "content": "import React, {\n  ButtonHTMLAttributes,\n  DetailedHTMLProps,\n  ReactNode,\n} from \"react\";\n\nconst colorClassnames = {\n  green: \"text-button green-mac-button\",\n  yellow: \"text-button yellow-mac-button\",\n  red: \"text-button red-mac-button\",\n};\n\nexport type ButtonProps = DetailedHTMLProps<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  HTMLButtonElement\n> & {\n  color?: keyof typeof colorClassnames;\n  icon?: ReactNode;\n};\n\nexport const MacButton: React.FC<ButtonProps> = ({\n  children,\n  color = \"green\",\n  icon,\n  className = \"\",\n  ...props\n}) => {\n  return (\n    <button\n      className={`flex text-xs rounded-md transition duration-200 ease-in-out \n      ${colorClassnames[color]} font-bold flex items-center justify-center\n       ${className} mac-buttons\n       rounded-full focus:outline-none p-0`}\n      data-testid=\"button\"\n      {...props}\n    >\n      <span className=\"flex items-center\">\n        {icon ? <span className={`items-center`}>{icon}</span> : null}\n        {children}\n      </span>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/MainGrid.tsx",
    "content": "import React from \"react\";\nimport { useScreenType } from \"../shared-hooks/useScreenType\";\n\ninterface DashboardGridProps {\n  className?: string;\n}\n\nexport const MainInnerGrid: React.FC<DashboardGridProps> = ({\n  children,\n  className = \"\",\n}) => {\n  const screenType = useScreenType();\n\n  let gridTemplateColumns = \"235px 640px 325px\";\n  let myClassName = ``;\n\n  if (screenType === \"2-cols\") {\n    gridTemplateColumns = \"60px 640px 325px\";\n  } else if (screenType === \"1-cols\") {\n    gridTemplateColumns = \"60px 640px\";\n  } else if (screenType === \"fullscreen\") {\n    myClassName = \"w-full px-3\";\n    gridTemplateColumns = \"1fr\";\n  }\n\n  return (\n    <div\n      id=\"main\"\n      className={`relative ${myClassName} ${className}`}\n      style={{\n        display: screenType === \"fullscreen\" ? \"flex\" : \"grid\",\n        gridTemplateColumns,\n        columnGap: 60,\n      }}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport const MainGrid: React.FC<DashboardGridProps> = ({ children }) => {\n  return (\n    <div\n      className={`flex justify-center w-full min-h-screen bg-primary-900`}\n      data-testid=\"main-grid\"\n    >\n      <MainInnerGrid>{children}</MainInnerGrid>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/MessageElement.tsx",
    "content": "import React from \"react\";\nimport { formatDistance, fromUnixTime } from \"date-fns\";\n\nimport { SingleUser } from \"./UserAvatar\";\n\nexport interface MessageElementProps {\n  user: {\n    username: string;\n    avatar: string;\n    isOnline: boolean;\n  };\n  msg: {\n    ts: number;\n    text: string;\n  };\n}\n\ninterface MessageDateProps {\n  ts: number;\n}\n\nconst MessageDate: React.FC<MessageDateProps> = ({ ts }) => (\n  <span\n    className=\"text-primary-300 text-sm font-medium inline-block truncate\"\n    style={{\n      lineHeight: \"22px\",\n    }}\n  >\n    {formatDistance(fromUnixTime(ts), new Date())} ago\n  </span>\n);\n\nexport const MessageElement: React.FC<MessageElementProps> = ({\n  user,\n  msg,\n}) => {\n  return (\n    <div\n      className=\"items-center w-full px-4 md:bg-primary-800 md:border-b md:border-primary-600 cursor-pointer hover:bg-primary-700 bg-primary-900\"\n      data-testid=\"msg-element\"\n    >\n      <div className=\"flex mr-3\">\n        <SingleUser size=\"sm\" isOnline={user.isOnline} src={user.avatar} />\n      </div>\n      <div\n        className=\"flex-col py-3 border-b border-primary-600 md:border-none md:py-0\"\n        style={{\n          width: \"calc(100% - 50px)\",\n        }}\n      >\n        <div className=\"flex justify-between\">\n          <span\n            className=\"text-button font-bold inline-block truncate mr-1\"\n            style={{\n              lineHeight: \"22px\",\n            }}\n          >\n            {user.username}\n          </span>\n          <MessageDate ts={msg.ts} />\n        </div>\n        <div\n          className=\"block text-sm text-primary-300 font-medium truncate w-9/12\"\n          style={{\n            lineHeight: \"22px\",\n          }}\n        >\n          {msg.text}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/MessagesDropdown.tsx",
    "content": "import React from \"react\";\nimport { MessageElement, MessageElementProps } from \"./MessageElement\";\nimport { BaseOverlay } from \"./BaseOverlay\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\n\nexport interface MessagesDropdownProps {\n  messageList: MessageElementProps[];\n}\n\nexport const MessagesDropdown: React.FC<MessagesDropdownProps> = ({\n  messageList = [],\n}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <BaseOverlay\n      title={t(\"components.messagesDropdown.title\")}\n      actionButton={\n        messageList.length ? t(\"components.messagesDropdown.showMore\") : \"\"\n      }\n    >\n      {messageList.length > 0 ? (\n        messageList.map((message, idx) => (\n          <MessageElement {...message} key={idx} />\n        ))\n      ) : (\n        <div className=\"py-5 px-4\" data-testid=\"empty-state-msg\">\n          {t(\"components.messagesDropdown.noMessages\")}\n        </div>\n      )}\n    </BaseOverlay>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/MinimizedRoomCard.tsx",
    "content": "import React from \"react\";\nimport { BoxedIcon } from \"./BoxedIcon\";\nimport {\n  SolidDeafened,\n  SolidDeafenedOff,\n  SolidFullscreen,\n  SolidMicrophone,\n} from \"../icons\";\nimport { Button } from \"./Button\";\nimport { DurationTicker } from \"./DurationTicker\";\nimport SvgSolidMicrophoneOff from \"../icons/SolidMicrophoneOff\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\n\nexport interface MinimizedRoomCardProps {\n  onFullscreenClick?: () => void;\n  leaveLoading?: boolean;\n  room: {\n    name: string;\n    speakers: string[];\n    roomStartedAt: Date;\n    myself: {\n      isSpeaker: boolean;\n      isMuted: boolean;\n      switchMuted(): void;\n      isDeafened: boolean;\n      switchDeafened(): void;\n      leave(): void;\n    };\n  };\n}\n\nexport const MinimizedRoomCard: React.FC<MinimizedRoomCardProps> = ({\n  onFullscreenClick,\n  leaveLoading,\n  room,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  // gap-n only works with grid\n  return (\n    <div\n      className=\"bg-primary-800 border border-accent rounded-lg p-4 gap-4 grid max-w-md w-full\"\n      data-testid=\"minimized-room-card\"\n    >\n      <div className=\"gap-1 grid\">\n        <h4 className=\"text-primary-100 break-all overflow-hidden\">\n          {room.name}\n        </h4>\n        <div className=\"text-primary-300 overflow-ellipsis overflow-hidden w-auto\">\n          {room.speakers.join(\", \")}\n        </div>\n        <div className=\"text-accent\">\n          {room.myself.isSpeaker\n            ? t(\"components.bottomVoiceControl.speaker\")\n            : t(\"components.bottomVoiceControl.listener\")}{\" \"}\n          · <DurationTicker dt={room.roomStartedAt} />\n        </div>\n      </div>\n      <div className=\"flex flex-row\">\n        <div className=\"grid grid-cols-3 gap-2\">\n          {room.myself.isSpeaker ? (\n            <BoxedIcon\n              data-testid=\"mute\"\n              transition\n              hover={room.myself.isMuted}\n              onClick={room.myself.switchMuted}\n              className={\n                !room.myself.isMuted && !room.myself.isDeafened\n                  ? \"bg-accent hover:bg-accent-hover text-button\"\n                  : \"\"\n              }\n            >\n              {room.myself.isMuted || room.myself.isDeafened ? (\n                <SvgSolidMicrophoneOff />\n              ) : (\n                <SolidMicrophone />\n              )}\n            </BoxedIcon>\n          ) : null}\n          <BoxedIcon\n            data-testid=\"deafen\"\n            onClick={room.myself.switchDeafened}\n            className={\n              room.myself.isDeafened\n                ? \"bg-accent hover:bg-accent-hover text-button\"\n                : \"\"\n            }\n          >\n            {room.myself.isDeafened ? <SolidDeafenedOff /> : <SolidDeafened />}\n          </BoxedIcon>\n          <BoxedIcon transition onClick={onFullscreenClick}>\n            <SolidFullscreen />\n          </BoxedIcon>\n        </div>\n        <Button\n          transition\n          color=\"primary-300\"\n          loading={leaveLoading}\n          className=\"flex-grow ml-4\"\n          onClick={room.myself.leave}\n        >\n          {t(\"components.bottomVoiceControl.leave\")}\n        </Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Modal.tsx",
    "content": "import React from \"react\";\nimport ReactModal from \"react-modal\";\nimport { SolidPlus } from \"../icons\";\n\nconst customStyles = {\n  default: {\n    overlay: {\n      backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n      zIndex: 1000,\n    },\n    content: {\n      top: \"50%\",\n      left: \"50%\",\n      right: \"auto\",\n      bottom: \"auto\",\n      marginRight: \"-50%\",\n      borderRadius: 8,\n      padding: \"40px 40px 40px 40px\",\n      transform: \"translate(-50%, -50%)\",\n      backgroundColor: \"var(--color-primary-800)\",\n      border: \"none\",\n      maxHeight: \"80vh\",\n      width: \"90%\",\n      maxWidth: 530,\n    },\n  },\n  userPreview: {\n    overlay: {\n      backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n      zIndex: 1000,\n    },\n    content: {\n      top: \"50%\",\n      left: \"50%\",\n      right: \"auto\",\n      bottom: \"auto\",\n      marginRight: \"-50%\",\n      borderRadius: 8,\n      padding: 0,\n      transform: \"translate(-50%, -50%)\",\n      backgroundColor: \"var(--color-primary-900)\",\n      border: \"none\",\n      maxHeight: \"80vh\",\n      width: \"90%\",\n      maxWidth: 435,\n    },\n  },\n};\n\nexport const Modal: React.FC<\n  ReactModal[\"props\"] & { variant?: keyof typeof customStyles }\n> = ({ children, variant = \"default\", ...props }) => {\n  const onKeyDown = (event: React.KeyboardEvent) => {\n    const currentActive = document.activeElement;\n\n    if (event.key === \"ArrowLeft\") {\n      (currentActive?.previousElementSibling as HTMLElement)?.focus();\n    } else if (event.key === \"ArrowRight\") {\n      (currentActive?.nextElementSibling as HTMLElement)?.focus();\n    }\n  };\n\n  return (\n    <ReactModal\n      shouldCloseOnEsc\n      shouldFocusAfterRender\n      style={customStyles[variant]}\n      {...props}\n    >\n      <div className={`flex flex-col w-full`}>\n        <div className={`flex justify-end absolute right-3 top-3`}>\n          <button\n            className={`p-1 text-primary-100`}\n            onClick={(e) => props?.onRequestClose?.(e)}\n            data-testid=\"close-modal\"\n          >\n            <SolidPlus className={`transform rotate-45`} />\n          </button>\n        </div>\n        <div\n          tabIndex={-1}\n          className={`focus:outline-none`}\n          onKeyDown={onKeyDown}\n        >\n          {children}\n        </div>\n      </div>\n    </ReactModal>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NativeCheckbox.tsx",
    "content": "import React, { useState } from \"react\";\n\nexport interface SwitchProps {\n  checked: boolean;\n}\n\nexport const Switch: React.FC<SwitchProps> = ({ checked }) => {\n  return (\n    <div\n      className={`w-5.5 h-4 relative rounded-full border px-1 transition duration-400 ease-in-out-hard ${\n        checked ? \"border-primary-100\" : \"border-primary-300\"\n      }`}\n    >\n      <div\n        className={`w-2 h-2  rounded-full transition duration-400 ease-in-out-hard absolute top-2/4 left-2/4 transform -translate-y-1/2 ${\n          checked\n            ? \"translate-x-0 bg-primary-100\"\n            : \"-translate-x-full bg-primary-300\"\n        }`}\n      />\n    </div>\n  );\n};\n\nexport interface NativeCheckboxProps {\n  title: string;\n  subtitle: string;\n  onClick?: (num: number | undefined) => void;\n  checked?: boolean;\n  num?: number;\n}\n\nexport const NativeCheckbox: React.FC<NativeCheckboxProps> = ({\n  title,\n  subtitle,\n  onClick,\n  checked = false,\n  num,\n}) => {\n  return (\n    <button\n      className=\"w-full flex px-3 py-2 bg-primary-900 rounded-8 justify-between group\"\n      onClick={onClick ? () => onClick(num) : undefined}\n    >\n      <div className=\"flex flex-col items-start\">\n        <div\n          className={`${\n            checked\n              ? \"font-bold text-primary-100 transition duration-200\"\n              : \"font-bold group-hover:text-primary-100 text-primary-300 transition duration-200\"\n          }`}\n        >\n          {title}\n        </div>\n        <div className=\"text-primary-300\">{subtitle}</div>\n      </div>\n      <div className=\"flex items-center justify-center\">\n        <Switch checked={checked} />\n      </div>\n    </button>\n  );\n};\n\nexport interface NativeCheckboxControllerProps {\n  checkboxes: NativeCheckboxProps[];\n}\n\nexport const NativeCheckboxController: React.FC<NativeCheckboxControllerProps> = ({\n  checkboxes,\n}) => {\n  // Set checked items based on stored users selection\n  const [currentChecked, setCurrentChecked] = useState<Array<number>>([]);\n\n  const handleClick = (id: number | undefined) => {\n    if (id !== undefined) {\n      if (currentChecked.includes(id)) {\n        setCurrentChecked(currentChecked.filter((e) => e !== id));\n      } else {\n        setCurrentChecked([...currentChecked, id]);\n      }\n    }\n  };\n\n  return (\n    <div className=\"flex flex-col space-y-2\">\n      {checkboxes.map((c, i) => (\n        <NativeCheckbox\n          key={c.title + i}\n          onClick={handleClick}\n          title={c.title}\n          subtitle={c.subtitle}\n          num={i}\n          checked={currentChecked.includes(i)}\n        />\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NativeRadio.tsx",
    "content": "import React, { useState } from \"react\";\n\nexport interface NativeRadioProps {\n  icon?: React.ReactElement;\n  title: string;\n  subtitle: string;\n  checked?: boolean;\n  onClick?: (id: number | undefined) => void;\n  num?: number;\n}\n\nexport const NativeRadio: React.FC<NativeRadioProps> = ({\n  icon,\n  title,\n  subtitle,\n  checked = false,\n  onClick,\n  num,\n}) => {\n  return (\n    <button\n      className=\"w-full flex px-3 py-2 bg-primary-900 rounded-8 justify-between group\"\n      onClick={onClick ? () => onClick(num) : undefined}\n    >\n      <div className=\"flex\">\n        {icon ? (\n          <div className=\"mr-3 mt-1.5\">\n            {React.cloneElement(icon, { width: 10, height: 10 })}\n          </div>\n        ) : null}\n        <div className=\"flex flex-col items-start\">\n          <div\n            className={`font-bold group-hover:text-primary-100 transition duration-100 ${\n              checked ? \"text-primary-100\" : \"text-primary-300\"\n            }`}\n          >\n            {title}\n          </div>\n          <div className=\"text-primary-300\">{subtitle}</div>\n        </div>\n      </div>\n      <div className=\"flex items-center justify-center\">\n        <div className=\"w-4 h-4 relative\">\n          <div\n            className={`${\n              checked ? \"bg-accent\" : \"\"\n            } w-2 h-2 absolute top-2/4 left-2/4 rounded-full transform -translate-y-1/2 -translate-x-1/2 transition duration-100`}\n          ></div>\n          <div\n            className={`${\n              checked ? \"border-accent\" : \"border-primary-300\"\n            } border w-4 h-4 absolute top-2/4 left-2/4 rounded-full transform -translate-y-1/2 -translate-x-1/2 transition duration-100`}\n          ></div>\n        </div>\n      </div>\n    </button>\n  );\n};\n\nexport interface NativeRadioControllerProps {\n  radios: NativeRadioProps[];\n}\n\nexport const NativeRadioController: React.FC<NativeRadioControllerProps> = ({\n  radios,\n}) => {\n  const [current, setCurrent] = useState(0); // To be changed by the stored user selection\n\n  const handleClick = (id: number | undefined) => {\n    if (id !== undefined) {\n      setCurrent(id); // Probably would be easier to pass this func\n    }\n  };\n\n  return (\n    <div className=\"flex flex-col space-y-2\">\n      {radios.map((r, i) => (\n        <NativeRadio\n          key={r.title + i}\n          title={r.title}\n          subtitle={r.subtitle}\n          icon={r.icon}\n          checked={current === i}\n          onClick={handleClick}\n          num={i}\n        />\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NativeSelect.tsx",
    "content": "import React from \"react\";\n\ninterface NativeSelectProps {}\n\nexport const NativeSelect: React.FC<\n  React.ComponentPropsWithoutRef<\"select\">\n> = ({ children, className, ...props }) => {\n  return (\n    <select\n      className={`h-full bg-primary-700 text-primary-100 placeholder-primary-300 focus:outline-none rounded-8 px-4 py-2 appearance-none bg-no-repeat bg-auto ${className}`}\n      style={{\n        backgroundImage:\n          \"url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0wIDAuNUw1IDUuNUwxMCAwLjVIMFoiIGZpbGw9IiNERUUzRUEiLz4KPC9zdmc+Cgo=')\",\n        backgroundPosition: \"right 8.5px center\",\n      }}\n      {...props}\n    >\n      {children}\n    </select>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NotificationElement/FollowNotification.tsx",
    "content": "import React from \"react\";\nimport { GenericNotification } from \"./GenericNotification\";\nimport { SingleUser } from \"../UserAvatar/SingleUser\";\nimport { Button } from \"../Button\";\n\nexport interface FollowNotificationProps {\n  userAvatarSrc: string;\n  username: string;\n  userProfileLink?: string;\n  time: string;\n  isOnline?: boolean;\n  following?: boolean;\n}\n\nexport const FollowNotification: React.FC<FollowNotificationProps> = ({\n  userAvatarSrc,\n  isOnline = false,\n  username,\n  userProfileLink,\n  time,\n  following = false,\n}) => {\n  const icon = <SingleUser src={userAvatarSrc} size=\"sm\" isOnline={isOnline} />;\n\n  const notificationMsg = (\n    <>\n      <a\n        className=\"font-bold\"\n        {...(userProfileLink ? { href: userProfileLink } : {})}\n      >\n        {username}\n      </a>\n      <span>&nbsp;followed you</span>\n    </>\n  );\n\n  const followButton = (\n    <Button\n      size=\"small\"\n      color={following ? \"secondary\" : \"primary\"}\n      style={{ width: \"90px\" }}\n    >\n      {following ? \"Following\" : \"Follow back\"}\n    </Button>\n  );\n\n  return (\n    <GenericNotification\n      icon={icon}\n      notificationMsg={notificationMsg}\n      time={time}\n      actionButton={followButton}\n    />\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NotificationElement/GenericNotification.tsx",
    "content": "import React, { ReactNode } from \"react\";\nimport { SolidRocket } from \"../../icons\";\n\nexport interface GenericNotificationProps {\n  notificationMsg: ReactNode;\n  time: string;\n  actionButton?: ReactNode;\n  icon?: ReactNode;\n}\n\nexport const GenericNotification: React.FC<GenericNotificationProps> = ({\n  notificationMsg,\n  time,\n  actionButton,\n  icon,\n}) => {\n  return (\n    <div className=\"flex items-center w-full\">\n      <div className=\"flex mr-3 w-6 h-6\">\n        {icon ? icon : <SolidRocket className=\"text-primary-300\" />}\n      </div>\n      <div className=\"flex flex-col\">\n        <div className=\"flex text-primary-100 flex-wrap\">\n          {notificationMsg ? notificationMsg : \"you have a new notification\"}\n        </div>\n        <div className=\"flex text-primary-300 text-sm\">\n          {time ? time : \"some time ago\"}\n        </div>\n      </div>\n      {actionButton ? <div className=\"flex ml-auto\">{actionButton}</div> : null}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NotificationElement/LiveNotification.tsx",
    "content": "import React from \"react\";\nimport { GenericNotification } from \"./GenericNotification\";\nimport { Button } from \"../Button\";\nimport { SolidTime } from \"../../icons\";\n\nexport interface LiveNotificationProps {\n  username: string;\n  userProfileLink?: string;\n  time: string;\n  joined?: boolean;\n}\n\nexport const LiveNotification: React.FC<LiveNotificationProps> = ({\n  username,\n  userProfileLink,\n  time,\n  joined = false,\n}) => {\n  const icon = <SolidTime className=\"text-primary-300\" />;\n\n  const notificationMsg = (\n    <>\n      <a\n        className=\"font-bold\"\n        {...(userProfileLink ? { href: userProfileLink } : {})}\n      >\n        {username}\n      </a>\n      <span>&nbsp;is now live!</span>\n    </>\n  );\n\n  const followButton = (\n    <Button\n      size=\"small\"\n      color={joined ? \"secondary\" : \"primary\"}\n      style={{ width: \"90px\" }}\n    >\n      {joined ? \"Joined\" : \"Join room\"}\n    </Button>\n  );\n\n  return (\n    <GenericNotification\n      icon={icon}\n      notificationMsg={notificationMsg}\n      time={time}\n      actionButton={followButton}\n    />\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NotificationElement/NewRoomNotification.tsx",
    "content": "import React from \"react\";\nimport { GenericNotification } from \"./GenericNotification\";\nimport { Button } from \"../Button\";\nimport { SolidRocket } from \"../../icons\";\n\nexport interface NewRoomNotificationProps {\n  username: string;\n  userProfileLink?: string;\n  time: string;\n  joined?: boolean;\n}\n\nexport const NewRoomNotification: React.FC<NewRoomNotificationProps> = ({\n  username,\n  userProfileLink,\n  time,\n  joined = false,\n}) => {\n  const icon = <SolidRocket className=\"text-primary-300\" />;\n\n  const notificationMsg = (\n    <>\n      <a\n        className=\"font-bold\"\n        {...(userProfileLink ? { href: userProfileLink } : {})}\n      >\n        {username}\n      </a>\n      <span>&nbsp;created a room</span>\n    </>\n  );\n\n  const followButton = (\n    <Button\n      size=\"small\"\n      color={joined ? \"secondary\" : \"primary\"}\n      style={{ width: \"90px\" }}\n    >\n      {joined ? \"Joined\" : \"Join room\"}\n    </Button>\n  );\n\n  return (\n    <GenericNotification\n      icon={icon}\n      notificationMsg={notificationMsg}\n      time={time}\n      actionButton={followButton}\n    />\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/NotificationElement/index.tsx",
    "content": "export { GenericNotification } from \"./GenericNotification\";\nexport { FollowNotification } from \"./FollowNotification\";\nexport { LiveNotification } from \"./LiveNotification\";\nexport { NewRoomNotification } from \"./NewRoomNotification\";\n"
  },
  {
    "path": "kibbeh/src/ui/NotificationsDropdown.tsx",
    "content": "/* eslint-disable indent */\nimport React from \"react\";\nimport { BaseOverlay } from \"./BaseOverlay\";\nimport {\n  FollowNotification,\n  GenericNotification,\n  LiveNotification,\n  NewRoomNotification,\n} from \"./NotificationElement\";\n\nexport type NotificationsDropdownPropsData = {\n  time: string;\n} & (\n  | {\n      type: \"follow\";\n      username: string;\n      userAvatarSrc: string;\n    }\n  | { type: \"generic\"; notificationMsg: string }\n  | { type: \"live\"; username: string }\n  | { type: \"newroom\"; username: string }\n);\n\nexport interface NotificationsDropdownProps {\n  data: NotificationsDropdownPropsData[];\n}\n\nconst parseNotification = (props: NotificationsDropdownPropsData) => {\n  switch (props.type) {\n    case \"follow\": {\n      return (\n        <FollowNotification\n          userAvatarSrc={props.userAvatarSrc}\n          username={props.username}\n          time={props.time}\n        />\n      );\n    }\n    case \"generic\": {\n      return (\n        <GenericNotification\n          notificationMsg={props.notificationMsg}\n          time={props.time}\n        />\n      );\n    }\n    case \"live\": {\n      return <LiveNotification username={props.username} time={props.time} />;\n    }\n    case \"newroom\": {\n      return (\n        <NewRoomNotification username={props.username} time={props.time} />\n      );\n    }\n  }\n};\n\nexport const NotificationsDropdown: React.FC<NotificationsDropdownProps> = (\n  props\n) => {\n  return (\n    <div className=\"flex\" style={{ width: 444 }}>\n      <BaseOverlay title={\"Notifications\"}>\n        {props.data.map((p, i) => (\n          <div className={\"py-3 px-4\"} key={i}>\n            {parseNotification(p)}\n          </div>\n        ))}\n      </BaseOverlay>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileAbout.tsx",
    "content": "import React from \"react\";\nimport { SolidLink } from \"../icons\";\nimport { kFormatter } from \"../lib/kFormatter\";\nimport { TextParser } from \"../modules/display/TextParser\";\nimport { ApiPreloadLink } from \"../shared-components/ApiPreloadLink\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { UserBadgeLg, UserBadgeLgProps } from \"./UserBadgeLg\";\n\nexport interface ProfileAboutProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  username: string;\n  followers: number;\n  following: number;\n  description?: string;\n  link?: string;\n  tags: UserBadgeLgProps[];\n}\n\nexport const ProfileAbout: React.FC<ProfileAboutProps> = ({\n  username,\n  followers,\n  following,\n  description,\n  link,\n  tags,\n  className = \"\",\n}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <div\n      className={`mt-2 bg-primary-800 p-4 rounded-8 w-full leading-8 ${className}`}\n      style={{ maxWidth: 640 }}\n    >\n      <div className=\"text-primary-100 font-bold text-xl pb-4\">\n        {t(\"pages.viewUser.about\")} {username} {t(\"pages.viewUser.aboutSuffix\")}\n      </div>\n      <div className=\"flex mb-2\">\n        <div className=\"flex group mr-4\">\n          <ApiPreloadLink route=\"followers\" data={{ username }}>\n            <span className=\"text-primary-100 font-bold\">\n              {kFormatter(followers)}\n            </span>\n            <span className=\"text-primary-300 ml-1 lowercase group-hover:underline\">\n              {t(\"pages.viewUser.followers\")}\n            </span>\n          </ApiPreloadLink>\n        </div>\n        <div className=\"flex group\">\n          <ApiPreloadLink route=\"following\" data={{ username }}>\n            <span className=\"text-primary-100 font-bold\">\n              {kFormatter(following)}\n            </span>\n            <span className=\"text-primary-300 ml-1 lowercase group-hover:underline\">\n              {t(\"pages.viewUser.following\")}\n            </span>\n          </ApiPreloadLink>\n        </div>\n      </div>\n      <div className=\"text-primary-100 pb-4 whitespace-pre-wrap max-h-5l overflow-y-auto\">\n        {<TextParser>{description || \"\"}</TextParser>}\n      </div>\n      {link && (\n        <div className=\"flex flex-row items-center mb-4\">\n          <SolidLink className=\"mr-2\" />\n          <a\n            className=\"text-accent font-bold text-sm\"\n            href={link}\n            target=\"_blank\"\n            rel=\"noreferrer\"\n          >\n            {link.replace(/(^\\w+:|^)\\/\\//, \"\")}\n          </a>\n        </div>\n      )}\n      {tags.map((props: UserBadgeLgProps) => (\n        <div key={props.icon} className=\"mb-1\">\n          <UserBadgeLg {...props} />\n        </div>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileAdmin.tsx",
    "content": "import { BaseUser, wrap } from \"@dogehouse/kebab\";\nimport React, { useEffect, useState } from \"react\";\nimport { useConn } from \"../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { Button } from \"./Button\";\n\nexport interface ProfileAdminProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  user: BaseUser;\n  className?: string;\n}\n\nexport const ProfileAdmin: React.FC<ProfileAdminProps> = ({\n  user,\n  className = \"\",\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const conn = useConn();\n  const wrapper = wrap(conn);\n  const [contributions, setContributions] = useState(0);\n  const [isStaff, setIsStaff] = useState(false);\n  useEffect(() => {\n    setContributions(user.contributions);\n    setIsStaff(user.staff);\n  }, [user]);\n  return (\n    <div\n      className={`block mt-2 bg-primary-800 p-4 rounded-8 w-full leading-8 ${className}`}\n      style={{ maxWidth: 640 }}\n    >\n      <div>\n        <label className=\"inline-flex mb-4\">\n          <div className={`text-primary-100`}>\n            {t(\"pages.admin.contributions\")}:\n          </div>\n          <input\n            type=\"number\"\n            className=\"px-2 ml-2 text-primary-100 placeholder-primary-300 focus:outline-none bg-primary-700\"\n            value={contributions}\n            onChange={(e) => setContributions(Number(e.target.value))}\n          />\n          <Button\n            size=\"tiny\"\n            className=\"ml-4\"\n            onClick={() => {\n              wrapper.mutation.userAdminUpdate(user.id, {\n                contributions,\n              });\n            }}\n          >\n            {t(\"common.save\")}\n          </Button>\n        </label>\n      </div>\n      <div>\n        <label className=\"inline-flex mb-4\">\n          <div className={`text-primary-100`}>{t(\"pages.admin.staff\")}</div>\n          <input\n            type=\"checkbox\"\n            className=\"ml-2 mt-1\"\n            checked={isStaff}\n            onChange={(e) => setIsStaff(e.target.checked)}\n          />\n          <Button\n            size=\"tiny\"\n            className=\"ml-4\"\n            onClick={() => {\n              wrapper.mutation.userAdminUpdate(user.id, {\n                staff: isStaff,\n              });\n            }}\n          >\n            {t(\"common.save\")}\n          </Button>\n        </label>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileBlock.tsx",
    "content": "import React from \"react\";\n\nexport interface ProfileBlockProps {\n  top: React.ReactNode;\n  bottom: React.ReactNode;\n}\n\nexport const ProfileBlock: React.FC<ProfileBlockProps> = ({ top, bottom }) => {\n  return (\n    <div className=\"flex flex-1 flex-col overflow-y-auto\">\n      <div className=\"flex justify-between items-end mb-5 max-w-md\">{top}</div>\n      <div className=\"flex justify-between items-end mb-5 max-w-md\">\n        {bottom}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileHeader.tsx",
    "content": "import React, { ReactChild, useEffect, useState } from \"react\";\nimport { ProfileHeaderWrapper } from \"./ProfileHeaderWrapper\";\nimport { Button } from \"./Button\";\nimport { UserBadge } from \"./UserBadge\";\nimport { SingleUser } from \"./UserAvatar/SingleUser\";\nimport {\n  SolidCompass,\n  SolidFriends,\n  SolidMessages,\n  SolidPersonAdd,\n} from \"../icons\";\nimport { useTypeSafeMutation } from \"../shared-hooks/useTypeSafeMutation\";\nimport { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { useTypeSafeUpdateQuery } from \"../shared-hooks/useTypeSafeUpdateQuery\";\nimport { EditProfileModal } from \"../modules/user/EditProfileModal\";\nimport { usePreloadPush } from \"../shared-components/ApiPreloadLink\";\nimport { badge, Badges } from \"./UserSummaryCard\";\n\nexport interface ProfileHeaderProps {\n  displayName: string;\n  username: string;\n  children?: ReactChild;\n  pfp?: string;\n  canDM?: boolean;\n  isCurrentUser?: boolean;\n  user: UserWithFollowInfo;\n  badges?: badge[];\n}\n\nexport const ProfileHeader: React.FC<ProfileHeaderProps> = ({\n  displayName,\n  username,\n  user,\n  children,\n  canDM,\n  isCurrentUser,\n  pfp = \"https://dogehouse.tv/favicon.ico\",\n  badges = [],\n}) => {\n  const { mutateAsync, isLoading: followLoading } = useTypeSafeMutation(\n    \"follow\"\n  );\n  const {\n    mutateAsync: unblock,\n    isLoading: unblockLoading,\n  } = useTypeSafeMutation(\"userUnblock\");\n  const { mutateAsync: block, isLoading: blockLoading } = useTypeSafeMutation(\n    \"userBlock\"\n  );\n\n  const { t } = useTypeSafeTranslation();\n  const updater = useTypeSafeUpdateQuery();\n  const [showEditProfileModal, setShowEditProfileModal] = useState(false);\n  const preloadPush = usePreloadPush();\n  const update = useTypeSafeUpdateQuery();\n\n  return (\n    // @TODO: Add the cover api (once it's implemented)}\n    <ProfileHeaderWrapper\n      coverUrl={user.bannerUrl || \"https://source.unsplash.com/random\"}\n    >\n      <EditProfileModal\n        isOpen={showEditProfileModal}\n        onRequestClose={() => setShowEditProfileModal(false)}\n        onEdit={(d) => {\n          update([\"getUserProfile\", d.username], (x) =>\n            !x ? x : { ...x, ...d }\n          );\n          if (d.username !== username) {\n            preloadPush({\n              route: \"profile\",\n              data: { username: d.username },\n            });\n          }\n        }}\n      />\n      <div className=\"flex mr-4 \">\n        <SingleUser\n          isOnline={user.online}\n          className=\"absolute flex-none -top-5.5 rounded-full shadow-outlineLg bg-primary-900\"\n          src={pfp}\n        />\n      </div>\n      <div className=\"flex flex-col w-3/6 font-sans\">\n        <h4 className=\"text-primary-100 font-bold truncate\">\n          {displayName || username}\n        </h4>\n        <div className=\"flex flex-row items-center\">\n          <p\n            className=\"text-primary-300 mr-2\"\n            data-testid=\"profile-info-username\"\n          >{`@${username}`}</p>\n\n          {user.followsYou && (\n            <UserBadge color=\"grey\" variant=\"primary-700\">\n              {t(\"pages.viewUser.followsYou\")}\n            </UserBadge>\n          )}\n        </div>\n        <div className=\"mt-2 flex\">\n          <Badges badges={badges} />\n          {children}\n        </div>\n      </div>\n\n      <div className=\"sm:w-3/6\">\n        <div className=\"flex flex-row justify-end content-end gap-2\">\n          {!isCurrentUser && (\n            <Button\n              loading={blockLoading || unblockLoading}\n              size=\"small\"\n              color={user.iBlockedThem ? \"secondary\" : \"primary\"}\n              onClick={async () => {\n                if (user.iBlockedThem) {\n                  await unblock([user.id]);\n                  updater([\"getUserProfile\", username], (u) =>\n                    !u\n                      ? u\n                      : {\n                          ...u,\n                          iBlockedThem: false,\n                        }\n                  );\n                } else {\n                  await block([user.id]);\n                  updater([\"getUserProfile\", username], (u) =>\n                    !u\n                      ? u\n                      : {\n                          ...u,\n                          iBlockedThem: true,\n                        }\n                  );\n                }\n              }}\n            >\n              {user.iBlockedThem\n                ? t(\"pages.viewUser.unblock\")\n                : t(\"pages.viewUser.block\")}\n            </Button>\n          )}\n          {!isCurrentUser && (\n            <Button\n              loading={followLoading}\n              onClick={async () => {\n                await mutateAsync([user.id, !user.youAreFollowing]);\n                updater([\"getUserProfile\", username], (u) =>\n                  !u || \"error\" in u\n                    ? u\n                    : {\n                        ...u,\n                        numFollowers:\n                          u.numFollowers + (user.youAreFollowing ? -1 : 1),\n                        youAreFollowing: !user.youAreFollowing,\n                      }\n                );\n              }}\n              size=\"small\"\n              color={user.youAreFollowing ? \"secondary\" : \"primary\"}\n              icon={user.youAreFollowing ? null : <SolidFriends />}\n            >\n              {user.youAreFollowing\n                ? t(\"pages.viewUser.unfollow\")\n                : t(\"pages.viewUser.followHim\")}\n            </Button>\n          )}\n          {isCurrentUser ? (\n            <Button\n              size=\"small\"\n              color=\"secondary\"\n              onClick={() => setShowEditProfileModal(true)}\n              icon={<SolidCompass />}\n            >\n              {t(\"pages.viewUser.editProfile\")}\n            </Button>\n          ) : (\n            \"\"\n          )}\n          {canDM ? (\n            <Button size=\"small\" color=\"secondary\" icon={<SolidMessages />}>\n              {t(\"pages.viewUser.sendDM\")}\n            </Button>\n          ) : (\n            \"\"\n          )}\n        </div>\n      </div>\n    </ProfileHeaderWrapper>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileHeaderWrapper.tsx",
    "content": "import React, { ReactNode } from \"react\";\n\nexport interface ProfileHeaderWrapperProps {\n  children: ReactNode;\n  coverUrl: string;\n}\n\nexport const ProfileHeaderWrapper: React.FC<ProfileHeaderWrapperProps> = ({\n  children,\n  coverUrl,\n  ...props\n}) => {\n  return (\n    <div className=\"bg-primary-800 rounded-8 relative\" {...props}>\n      <img\n        alt=\"cover\"\n        src={coverUrl}\n        className=\"rounded-t-8 w-full object-cover\"\n        style={{ height: \"155px\" }}\n      />\n      <div className=\"container mx-auto sm:flex p-4 relative\">{children}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileScheduled.tsx",
    "content": "import { ScheduledRoom, UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { ScheduledRoomCard } from \"../modules/scheduled-rooms/ScheduledRoomCard\";\nimport { useTypeSafeQuery } from \"../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeUpdateQuery } from \"../shared-hooks/useTypeSafeUpdateQuery\";\nimport { Button } from \"./Button\";\nimport { CenterLoader } from \"./CenterLoader\";\nimport { EditScheduleRoomModalController } from \"../modules/scheduled-rooms/EditScheduleRoomModalController\";\n\nexport interface ProfileScheduledProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  user: UserWithFollowInfo;\n}\n\nconst List = ({\n  onLoadMore,\n  cursor,\n  isLastPage,\n  isOnlyPage,\n  userId,\n  onEdit,\n}: {\n  onEdit: (sr: { scheduleRoomToEdit: ScheduledRoom; cursor: string }) => void;\n  userId: string;\n  cursor: string;\n  isLastPage: boolean;\n  isOnlyPage: boolean;\n  onLoadMore: (o: string) => void;\n}) => {\n  const { isLoading, data } = useTypeSafeQuery(\n    [\"getScheduledRooms\", cursor, \"all\", userId],\n    { staleTime: Infinity, refetchOnMount: \"always\" },\n    [cursor, \"all\", userId]\n  );\n  const update = useTypeSafeUpdateQuery();\n  const { t } = useTypeSafeTranslation();\n\n  if (isLoading) {\n    return <CenterLoader />;\n  }\n\n  if (!data) {\n    return null;\n  }\n\n  if (isOnlyPage && data.rooms.length === 0) {\n    return (\n      <div\n        className={`mt-2 bg-primary-800 p-4 rounded-8 w-full leading-8 text-primary-100`}\n      >\n        {t(\"modules.scheduledRooms.noneFound\")}\n      </div>\n    );\n  }\n\n  return (\n    <div className={`${isLastPage ? \"mb-24\" : \"\"}`}>\n      {data.rooms.map((r) => (\n        <div className={`mt-4`} key={r.id}>\n          <ScheduledRoomCard\n            onDeleteComplete={() => {\n              update([\"getScheduledRooms\", cursor, \"all\", userId], (d) => {\n                return {\n                  rooms: (d?.rooms || []).filter((x) => x.id !== r.id),\n                  nextCursor: d?.nextCursor,\n                };\n              });\n            }}\n            onEdit={() => onEdit({ cursor, scheduleRoomToEdit: r })}\n            info={r}\n          />\n        </div>\n      ))}\n      {isLastPage && data.nextCursor ? (\n        <div className={`flex justify-center my-4`}>\n          <Button size=\"small\" onClick={() => onLoadMore(data.nextCursor!)}>\n            {t(\"common.loadMore\")}\n          </Button>\n        </div>\n      ) : null}\n    </div>\n  );\n};\n\nexport const ProfileScheduled: React.FC<ProfileScheduledProps> = ({\n  user,\n  className = \"\",\n}) => {\n  const [{ cursors, userId }, setQueryState] = useState<{\n    cursors: string[];\n    userId: string;\n  }>({ cursors: [\"\"], userId: user.id });\n  const update = useTypeSafeUpdateQuery();\n\n  return (\n    <div\n      className={`mt-2 rounded-8 w-full leading-8 ${className}`}\n      style={{ maxWidth: 640 }}\n    >\n      <EditScheduleRoomModalController\n        onScheduledRoom={(editInfo, data, _resp) => {\n          update([\"getScheduledRooms\", editInfo.cursor, userId], (d) => {\n            return {\n              rooms: (d?.rooms || []).map((x) =>\n                x.id === editInfo.scheduleRoomToEdit.id\n                  ? {\n                      ...x,\n                      name: data.name,\n                      description: data.description,\n                      scheduledFor: data.scheduledFor.toISOString(),\n                    }\n                  : x\n              ),\n              nextCursor: d?.nextCursor,\n            };\n          });\n        }}\n      >\n        {({ onEdit }) =>\n          cursors.map((cursor, i) => (\n            <List\n              userId={userId}\n              onLoadMore={(o) =>\n                setQueryState({\n                  cursors: [...cursors, o],\n                  userId,\n                })\n              }\n              onEdit={onEdit}\n              isOnlyPage={cursors.length === 1}\n              isLastPage={cursors.length - 1 === i}\n              key={cursor}\n              cursor={cursor}\n            />\n          ))\n        }\n      </EditScheduleRoomModalController>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/ProfileTabs.tsx",
    "content": "import { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport { useConn } from \"../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { ProfileAbout } from \"./ProfileAbout\";\nimport { ProfileAdmin } from \"./ProfileAdmin\";\nimport { ProfileScheduled } from \"./ProfileScheduled\";\nimport { UserBadgeLgProps } from \"./UserBadgeLg\";\n\nexport interface ProfileTabsProps extends React.HTMLAttributes<HTMLDivElement> {\n  user: UserWithFollowInfo;\n  tabs?: {\n    about?: boolean;\n    rooms?: boolean;\n    scheduled?: boolean;\n    recorded?: boolean;\n    clips?: boolean;\n  };\n  aboutTags?: UserBadgeLgProps[];\n}\n\nexport const ProfileTabs: React.FC<ProfileTabsProps> = ({\n  className,\n  user,\n  tabs = {\n    about: true,\n    scheduled: true,\n    rooms: false,\n    recorded: false,\n    clips: false,\n  },\n  aboutTags = [],\n  ...props\n}) => {\n  const [activeTab, setActiveTab] = useState(\"about\");\n  const { t } = useTypeSafeTranslation();\n  const conn = useConn();\n  return (\n    <>\n      <div\n        className={`w-full flex items-center justify-around ${className}`}\n        {...props}\n      >\n        <button\n          className={`py-1 text-primary-100 text-base font-bold border-b-2 border-primary-900 transition hover:border-accent focus:outline-no-chrome\n               ${activeTab === \"about\" && `border-accent text-accent`} ${\n            !tabs.about ? \"hidden\" : \"\"\n          }`}\n          data-testid={`user:${user.username}:tab:about`}\n          onClick={() => setActiveTab(\"about\")}\n        >\n          {t(\"pages.viewUser.profileTabs.about\")}\n        </button>\n\n        <button\n          className={`py-1 text-primary-100 text-base font-bold border-b-2 border-primary-900 transition hover:border-accent focus:outline-no-chrome\n               ${activeTab === \"rooms\" && `border-accent text-accent`} ${\n            !tabs.rooms ? \"hidden\" : \"\"\n          }`}\n          data-testid={`user:${user.username}:tab:rooms`}\n          onClick={() => setActiveTab(\"rooms\")}\n        >\n          {t(\"pages.viewUser.profileTabs.rooms\")}\n        </button>\n        <button\n          className={`py-1 text-primary-100 text-base font-bold border-b-2 border-primary-900 transition hover:border-accent focus:outline-no-chrome\n               ${activeTab === \"scheduled\" && `border-accent text-accent`} ${\n            !tabs.scheduled ? \"hidden\" : \"\"\n          }`}\n          onClick={() => setActiveTab(\"scheduled\")}\n          data-testid={`user:${user.username}:tab:scheduled`}\n        >\n          {t(\"pages.viewUser.profileTabs.scheduled\")}\n        </button>\n        <button\n          className={`py-1 text-primary-100 text-base font-bold border-b-2 border-primary-900 transition hover:border-accent focus:outline-no-chrome\n               ${activeTab === \"recorded\" && `border-accent text-accent`} ${\n            !tabs.recorded ? \"hidden\" : \"\"\n          }`}\n          onClick={() => setActiveTab(\"recorded\")}\n          data-testid={`user:${user.username}:tab:recorded`}\n        >\n          {t(\"pages.viewUser.profileTabs.recorded\")}\n        </button>\n        <button\n          className={`py-1 text-primary-100 text-base font-bold border-b-2 border-primary-900 transition hover:border-accent focus:outline-no-chrome\n               ${activeTab === \"clips\" && `border-accent text-accent`} ${\n            !tabs.clips ? \"hidden\" : \"\"\n          }`}\n          onClick={() => setActiveTab(\"clips\")}\n          data-testid={`user:${user.username}:tab:clips`}\n        >\n          {t(\"pages.viewUser.profileTabs.clips\")}\n        </button>\n\n        <button\n          className={`py-1 text-primary-100 text-base font-bold border-b-2 border-primary-900 transition hover:border-accent focus:outline-no-chrome\n               ${activeTab === \"admin\" && `border-accent text-accent`} ${\n            conn.user.staff && conn.user.id !== user.id ? \"\" : \"hidden\"\n          }`}\n          onClick={() => setActiveTab(\"admin\")}\n          data-testid={`user:${user.username}:tab:admin`}\n        >\n          {t(\"pages.viewUser.profileTabs.admin\")}\n        </button>\n      </div>\n\n      <div>\n        <ProfileAbout\n          className={activeTab !== \"about\" ? \"hidden\" : \"\"}\n          username={user.username}\n          followers={user.numFollowers}\n          following={user.numFollowing}\n          description={user.bio}\n          tags={aboutTags}\n        />\n\n        <ProfileScheduled\n          user={user}\n          className={activeTab !== \"scheduled\" ? \"hidden\" : \"\"}\n        />\n        <ProfileAdmin\n          className={activeTab !== \"admin\" ? \"hidden\" : \"\"}\n          user={user}\n        />\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomAvatar.tsx",
    "content": "import React from \"react\";\nimport { useDebugAudioStore } from \"../global-stores/useDebugAudio\";\nimport { AudioDebugAvatar } from \"../modules/debugging/AudioDebugAvatar\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { SingleUser } from \"./UserAvatar\";\n\ninterface RoomAvatarProps {\n  isMe?: boolean;\n  id?: string;\n  canSpeak?: boolean;\n  activeSpeaker?: boolean;\n  muted?: boolean;\n  deafened?: boolean;\n  username: string;\n  flair?: React.ReactNode;\n  src: string;\n  isBot?: boolean;\n  onClick?: () => void;\n}\n\nexport const RoomAvatar: React.FC<RoomAvatarProps> = ({\n  isMe,\n  src,\n  username,\n  flair,\n  muted,\n  deafened,\n  onClick,\n  canSpeak,\n  id,\n  activeSpeaker,\n  isBot,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const { debugAudio } = useDebugAudioStore();\n  const avatar = (\n    <SingleUser\n      activeSpeaker={activeSpeaker}\n      size=\"lg\"\n      src={src}\n      muted={muted}\n      isBot={isBot}\n      deafened={deafened}\n      username={username}\n      hover={true}\n    />\n  );\n  return (\n    <button\n      data-testid={`room:user:node:${username}`}\n      className={`flex flex-col items-center`}\n      onClick={onClick}\n    >\n      {!isMe && canSpeak && id && debugAudio ? (\n        <AudioDebugAvatar id={id}>{avatar}</AudioDebugAvatar>\n      ) : (\n        avatar\n      )}\n      <div className={`flex items-center mt-2 ${deafened ? \"opacity-60\" : \"\"}`}>\n        <span className={`truncate text-primary-100 text-sm block`}>\n          {username}\n        </span>\n        {flair}\n      </div>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomCard.tsx",
    "content": "import { format, isToday, isPast, differenceInMilliseconds } from \"date-fns\";\nimport React, { useEffect, useState } from \"react\";\nimport { SolidTime } from \"../icons\";\nimport { BubbleText } from \"./BubbleText\";\nimport { RoomCardHeading } from \"./RoomCardHeading\";\nimport { Tag } from \"./Tag\";\nimport { MultipleUsers, SingleUser } from \"./UserAvatar\";\n\nexport function formatNumber(num: number): string {\n  return Math.abs(num) > 999\n    ? `${Math.sign(num) * Number((Math.abs(num) / 1000).toFixed(1))}K`\n    : `${Math.sign(num) * Math.abs(num)}`;\n}\n\nfunction useScheduleRerender(scheduledFor?: Date) {\n  // same logic stolen from kofta, rerenders\n  // at the scheduleFor date\n  const [, rerender] = useState(0);\n\n  useEffect(() => {\n    if (!scheduledFor) {\n      return;\n    }\n\n    let done = false;\n    const id = setTimeout(() => {\n      done = true;\n      rerender((x) => x + 1);\n    }, differenceInMilliseconds(scheduledFor, new Date()) + 1000);\n\n    return () => {\n      if (!done) {\n        clearTimeout(id);\n      }\n    };\n  }, [scheduledFor]);\n}\n\nexport type RoomCardProps = {\n  title: string;\n  subtitle: string;\n  avatars: string[];\n  scheduledFor?: Date;\n  listeners: number;\n  tags: React.ReactNode[];\n  onClick?: () => void;\n};\n\nexport const RoomCard: React.FC<RoomCardProps> = ({\n  title,\n  subtitle,\n  avatars,\n  scheduledFor,\n  listeners,\n  tags,\n  onClick,\n}) => {\n  useScheduleRerender(scheduledFor);\n\n  let scheduledForLabel = \"\";\n\n  if (scheduledFor) {\n    if (isToday(scheduledFor)) {\n      scheduledForLabel = format(scheduledFor, `K:mm a`);\n    } else {\n      scheduledForLabel = format(scheduledFor, `LLL d`);\n    }\n  }\n\n  const roomLive = !scheduledFor || isPast(scheduledFor);\n\n  return (\n    <button\n      data-testid={`room-card:${title}`}\n      onClick={onClick}\n      className=\"flex flex-col w-full p-4 rounded-lg transition duration-200 ease-in-out bg-primary-800 hover:bg-primary-700\"\n    >\n      <div className=\"flex justify-between w-full space-x-4\">\n        <RoomCardHeading\n          icon={roomLive ? undefined : <SolidTime />}\n          text={title}\n        />\n        <div className=\"flex flex-shrink-0\">\n          <BubbleText live={roomLive}>\n            {roomLive ? formatNumber(listeners) : scheduledForLabel}\n          </BubbleText>\n        </div>\n      </div>\n      <div className=\"w-full mt-2 flex\">\n        {avatars.length > 0 ? (\n          <MultipleUsers className=\"mr-2\" srcArray={avatars} />\n        ) : null}\n        <div className=\"text-left break-all truncate whitespace-pre-wrap line-clamp-2 text-primary-300\">\n          {subtitle}\n        </div>\n      </div>\n      <div className=\"flex mt-4 space-x-2\">\n        {tags.map((tag, idx) => (\n          <Tag key={idx}>{tag}</Tag>\n        ))}\n      </div>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomCardHeading.tsx",
    "content": "import React, { ReactElement } from \"react\";\n\nexport interface RoomCardHeadingProps {\n  icon?: ReactElement;\n  text: string;\n}\n\nexport const RoomCardHeading: React.FC<RoomCardHeadingProps> = ({\n  icon,\n  text,\n}) => {\n  return (\n    <div className=\"flex text-primary-100 font-bold leading-5 truncate w-full\">\n      {icon ? <span className=\"mr-2 align-middle\">{icon}</span> : null}\n      <span className=\"inline truncate\">{text}</span>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomCardParticipants.tsx",
    "content": "import React from \"react\";\nimport { MultipleUsers } from \"./UserAvatar/MultipleUsers\";\n\nexport interface RoomCardParticipantsProps {\n  users: { name: string; picture: string }[];\n  className?: string;\n}\n\nexport const RoomCardParticipants: React.FC<RoomCardParticipantsProps> = ({\n  users,\n  className,\n  ...props\n}) => {\n  const pics = users.map((u) => u.picture);\n  const names = users.map((u) => u.name).join(\", \");\n\n  return (\n    <div className={`flex space-x-1 ${className ? className : \"\"}`} {...props}>\n      <MultipleUsers srcArray={pics} />\n      <div className={`flex table-cell`}>\n        <p\n          className={`text-primary-300 truncate`}\n          style={{ fontSize: \"0.9rem\" }}\n        >\n          {names}\n        </p>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomHeader.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { SolidCaretRight } from \"../icons\";\nimport { ApiPreloadLink } from \"../shared-components/ApiPreloadLink\";\nimport { linkRegex } from \"../lib/constants\";\nimport normalizeUrl from \"normalize-url\";\n\ninterface RoomHeaderProps {\n  onTitleClick?: () => void;\n  title: string;\n  names: string[];\n  description: string;\n}\n\nexport const RoomHeader: React.FC<RoomHeaderProps> = ({\n  onTitleClick,\n  title,\n  names,\n  description,\n}) => {\n  const [open, setOpen] = useState(true);\n  const [hasDescription, setHasDescription] = useState<boolean>(false);\n\n  useEffect(() => {\n    setHasDescription(description.trim().length > 0);\n  }, [description]);\n\n  return (\n    <div\n      className={`flex flex-col p-4 bg-primary-800 rounded-t-8 border-b border-primary-600 w-full ${\n        hasDescription ? \"cursor-pointer\" : \"\"\n      }`}\n      onClick={hasDescription ? () => setOpen(!open) : undefined}\n    >\n      <div className={`flex text-primary-100 mb-2`}>\n        <button\n          onClick={onTitleClick}\n          className={`flex text-xl font-bold flex-1 truncate`}\n          data-testid=\"room-title\"\n        >\n          {title}\n        </button>\n        {hasDescription && (\n          <button className=\"flex\" onClick={() => setOpen(!open)}>\n            <SolidCaretRight\n              className={`transform ${\n                open ? \"-rotate-90 mt-auto\" : \"mr-auto rotate-90\"\n              } cursor-pointer`}\n              width={20}\n              height={20}\n            />\n          </button>\n        )}\n      </div>\n      <div className={`flex text-primary-200 text-sm`}>\n        <span style={{ marginRight: 4 }}>with</span>{\" \"}\n        {names.map((username, i) => (\n          <ApiPreloadLink\n            route=\"profile\"\n            data={{ username }}\n            key={username + i}\n          >\n            <span\n              className={`font-bold text-primary-100 hover:underline`}\n              style={{ marginRight: 4 }}\n            >\n              {`${username}`}\n              {i === names.length - 1 ? \"\" : `,`}\n            </span>\n          </ApiPreloadLink>\n        ))}\n      </div>\n      {/* {open ? <div className=\"text-primary-100 mt-4\">{description}</div> : null} */}\n\n      {open && description?.trim() && (\n        <div\n          className=\"mt-4 overflow-y-auto break-words\"\n          style={{ maxHeight: \"100px\" }}\n        >\n          {description.split(/\\n/).map(\n            (line, i) =>\n              line.trim() && (\n                <div key={i}>\n                  {line.split(\" \").map((chunk, j) => {\n                    try {\n                      return linkRegex.test(chunk) ? (\n                        <a\n                          href={normalizeUrl(chunk)}\n                          rel=\"noreferrer\"\n                          className=\"text-accent text-center hover:underline inline break-all\"\n                          key={`${i}${j}`}\n                          target=\"_blank\"\n                        >\n                          {chunk}&nbsp;\n                        </a>\n                      ) : (\n                        <span\n                          className=\"text-primary-100\"\n                          key={`${i}${j}`}\n                        >{`${chunk} `}</span>\n                      );\n                    } catch (err) {}\n\n                    return null;\n                  })}\n\n                  <br />\n                </div>\n              )\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomPanelIconBar.tsx",
    "content": "import React from \"react\";\nimport {\n  SolidChatBubble,\n  SolidDeafened,\n  SolidDeafenedOff,\n  SolidFriendsAdd,\n  SolidMicrophone,\n  SolidMicrophoneOff,\n  SolidSettings,\n} from \"../icons\";\nimport { useScreenType } from \"../shared-hooks/useScreenType\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { BoxedIcon } from \"./BoxedIcon\";\nimport { Button } from \"./Button\";\n\ninterface RoomPanelIconBarProps {\n  mute?: {\n    isMuted: boolean;\n    onMute: () => void;\n  };\n  deaf?: {\n    isDeaf: boolean;\n    onDeaf: () => void;\n  };\n  onInvitePeopleToRoom?: () => void;\n  onRoomSettings?: () => void;\n  onLeaveRoom(): void;\n  onToggleChat(): void;\n}\n\nexport const RoomPanelIconBar: React.FC<RoomPanelIconBarProps> = ({\n  mute,\n  deaf,\n  onInvitePeopleToRoom,\n  onRoomSettings,\n  onLeaveRoom,\n  onToggleChat,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const screenType = useScreenType();\n  return (\n    <div className=\"flex flex-wrap justify-center bg-primary-700 rounded-b-8 py-3 px-4 w-full sm:justify-between\">\n      <div className=\"flex my-1 justify-between w-full sm:my-0 sm:w-auto\">\n        {mute ? (\n          <BoxedIcon\n            transition\n            hover={!mute.isMuted}\n            className={`mx-1 w-11 h-6.5 ${\n              !mute.isMuted && !deaf?.isDeaf\n                ? `bg-accent hover:bg-accent-hover text-button`\n                : ``\n            }`}\n            color=\"800\"\n            title={t(\"components.bottomVoiceControl.toggleMuteMicBtn\")}\n            onClick={() => mute.onMute()}\n            data-testid=\"mute\"\n          >\n            {mute.isMuted || deaf?.isDeaf ? (\n              <SolidMicrophoneOff width=\"20\" height=\"20\" />\n            ) : (\n              <SolidMicrophone width=\"20\" height=\"20\" />\n            )}\n          </BoxedIcon>\n        ) : null}\n        {deaf ? (\n          <BoxedIcon\n            transition\n            hover={deaf.isDeaf}\n            className={`mx-1 h-6.5 w-6.5 ${\n              deaf.isDeaf ? `bg-accent hover:bg-accent-hover text-button` : ``\n            }`}\n            color=\"800\"\n            title={t(\"components.bottomVoiceControl.toggleDeafMicBtn\")}\n            onClick={() => deaf.onDeaf()}\n            data-testid=\"deafen\"\n          >\n            {deaf.isDeaf ? (\n              <SolidDeafenedOff width=\"20\" height=\"20\" />\n            ) : (\n              <SolidDeafened width=\"20\" height=\"20\" />\n            )}\n          </BoxedIcon>\n        ) : null}\n        {onInvitePeopleToRoom ? (\n          <BoxedIcon\n            transition\n            className=\"mx-1 h-6.5 w-6.5\"\n            color=\"800\"\n            title={t(\"components.bottomVoiceControl.inviteUsersToRoomBtn\")}\n            onClick={onInvitePeopleToRoom}\n            data-testid=\"invite-friends\"\n          >\n            <SolidFriendsAdd height=\"20\" />\n          </BoxedIcon>\n        ) : null}\n        {screenType === \"1-cols\" ? (\n          <BoxedIcon\n            transition\n            className=\"mx-1 h-6.5 w-6.5\"\n            color=\"800\"\n            onClick={onToggleChat}\n            data-testid=\"chat\"\n          >\n            <SolidChatBubble />\n          </BoxedIcon>\n        ) : null}\n        {onRoomSettings ? (\n          <BoxedIcon\n            transition\n            className=\"mx-1 h-6.5 w-6.5\"\n            color=\"800\"\n            title={t(\"components.bottomVoiceControl.settings\")}\n            onClick={onRoomSettings}\n            data-testid=\"room-settings\"\n          >\n            <SolidSettings width=\"20\" height=\"20\" />\n          </BoxedIcon>\n        ) : null}\n      </div>\n\n      <Button\n        transition\n        className={`my-1 mx-1 w-full text-base sm:my-0 sm:mx-0 sm:w-15`}\n        color=\"secondary-800\"\n        onClick={() => {\n          onLeaveRoom();\n        }}\n        data-testid=\"leave-room\"\n      >\n        {t(\"components.bottomVoiceControl.leave\")}\n      </Button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/RoomSectionHeader.tsx",
    "content": "import React from \"react\";\n\ninterface RoomSectionHeaderProps {\n  title: string;\n  tagText: string;\n}\n\nexport const RoomSectionHeader: React.FC<RoomSectionHeaderProps> = ({\n  title,\n  tagText,\n}) => {\n  return (\n    <div className={`flex items-center col-span-full`}>\n      <div className={`flex mr-2 font-bold text-xl text-primary-100`}>\n        {title}\n      </div>\n      <div\n        style={{ height: 18 }}\n        className={`bg-primary-600 rounded-5 px-2 text-sm font-bold text-primary-100 items-center justify-center`}\n      >\n        {tagText}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Search/GlobalSearch.tsx",
    "content": "import { Room, User } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport { SearchBar } from \"./SearchBar\";\nimport SearchHistory from \"./SearchHistory\";\nimport { SearchOverlay } from \"./SearchOverlay\";\nimport { RoomSearchResult, UserSearchResult } from \"./SearchResult\";\n\nexport type HistoryItem = {\n  id: string;\n  term: string;\n};\n\nexport type UserSearchResult = {\n  id: string;\n  avatar: string;\n  displayName: string;\n  username: string;\n  isOnline: boolean;\n};\n\nexport type RoomSearchResult = {\n  id: string;\n  displayName: string;\n  hosts: string[];\n  userCount: number;\n};\n\nexport type SearchResultItem = User | Room;\n\nexport interface GlobalSearchProps {\n  history: HistoryItem[];\n  searchResults: SearchResultItem[];\n}\n\nexport interface HistoryProps {\n  history: HistoryItem[];\n}\n\nexport interface SearchResultProps {\n  items: SearchResultItem[];\n}\n\nconst History: React.FC<HistoryProps> = ({ history }) => {\n  const historyDeleteClickHandler = (id: string) => {\n    return id;\n  };\n\n  return (\n    <div className=\"flex flex-col w-full\">\n      {history.map((h) => (\n        <SearchHistory\n          onClickToDeleteSearchHistory={() => historyDeleteClickHandler(h.id)}\n          key={h.id}\n          searchText={h.term}\n        />\n      ))}\n    </div>\n  );\n};\n\nconst SearchResult: React.FC<SearchResultProps> = ({ items }) => {\n  return (\n    <div className=\"flex flex-col w-full\">\n      {items.map((userOrRoom, i) =>\n        \"name\" in userOrRoom ? (\n          <RoomSearchResult key={i} room={userOrRoom} />\n        ) : (\n          <UserSearchResult key={i} user={userOrRoom} />\n        )\n      )}\n    </div>\n  );\n};\n\nexport const GlobalSearch: React.FC<GlobalSearchProps> = ({\n  history,\n  searchResults,\n}) => {\n  const [focused, setFocused] = useState(false);\n  const [term, setTerm] = useState(\"\");\n\n  const setSearchTerm = ({\n    currentTarget: { value },\n  }: React.FormEvent<HTMLInputElement>) => setTerm(value);\n  const focusHandler = () => setFocused(true);\n  const blurHandler = () => setFocused(false);\n\n  return (\n    <div className=\"flex w-full relative\">\n      <div className=\"flex relative z-10 w-full p-2\">\n        <SearchBar\n          className=\"mb-2\"\n          onFocus={focusHandler}\n          onBlur={blurHandler}\n          onChange={setSearchTerm}\n        />\n      </div>\n      {focused && (\n        <SearchOverlay className=\"absolute z-0\">\n          <div className=\"flex flex-col w-full\">\n            {!term && history && <History history={history} />}\n            {term && searchResults && <SearchResult items={searchResults} />}\n          </div>\n        </SearchOverlay>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Search/SearchBar.tsx",
    "content": "import React from \"react\";\nimport { SolidSearch } from \"../../icons\";\nimport { Input } from \"../Input\";\nimport { Spinner } from \"../Spinner\";\n\nexport interface SearchBarProps\n  extends React.ComponentPropsWithoutRef<\"input\"> {\n  inputClassName?: string;\n  mobile?: boolean;\n  isLoading?: boolean;\n}\n\nexport const SearchBar: React.FC<SearchBarProps> = ({\n  className = \"\",\n  inputClassName = \"\",\n  isLoading = false,\n  mobile = false,\n  ...props\n}) => {\n  return (\n    <div\n      className={`items-center flex w-full bg-primary-700 text-primary-300 transition duration-200 ease-in-out focus-within:text-primary-100 rounded-lg ${\n        mobile ? \"px-4\" : \"\"\n      } ${className}`}\n    >\n      {!mobile && (\n        <div className=\"h-full mx-4 flex items-center pointer-events-none\">\n          <SolidSearch />\n        </div>\n      )}\n      <Input\n        autoFocus\n        data-testid=\"searchbar\"\n        className={`${inputClassName} pl-0`}\n        {...props}\n      />\n      {isLoading && (\n        <div\n          className={`h-full flex items-center pointer-events-none ${\n            !mobile && \"mx-4\"\n          }`}\n        >\n          <Spinner />\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Search/SearchHistory.tsx",
    "content": "import React from \"react\";\n\nimport { SolidSearch } from \"../../icons\";\n\nexport interface SearchHistoryProps {\n  onClickToDeleteSearchHistory: React.MouseEventHandler<HTMLSpanElement>;\n  searchText: string;\n}\n\nconst SearchHistory: React.FC<SearchHistoryProps> = ({\n  onClickToDeleteSearchHistory,\n  searchText,\n}) => {\n  return (\n    <div className={\"flex flex-row py-2 px-4 w-full\"}>\n      <div className={\"flex flex-1 items-center group cursor-pointer\"}>\n        <SolidSearch\n          className={\"mr-4 group-hover:text-primary-100 text-primary-300\"}\n        />\n        <span className={\"text-primary-300 group-hover:text-primary-100\"}>\n          {searchText}\n        </span>\n      </div>\n      <span\n        onClick={onClickToDeleteSearchHistory}\n        className={\"text-accent underline cursor-pointer\"}\n      >\n        Delete\n      </span>\n    </div>\n  );\n};\n\nexport default SearchHistory;\n"
  },
  {
    "path": "kibbeh/src/ui/Search/SearchOverlay.tsx",
    "content": "import React, { forwardRef, ReactElement } from \"react\";\nimport { getInitialProps } from \"react-i18next\";\n\nexport interface SearchOverlayProps {\n  children: ReactElement;\n  className?: string;\n}\n\nexport const SearchOverlay = forwardRef<\n  HTMLDivElement,\n  React.ComponentPropsWithoutRef<\"div\">\n>(({ children, className = \"\", ...props }, ref) => {\n  return (\n    <div\n      ref={ref}\n      className={`absolute flex flex-col py-2 rounded-8 bg-primary-800 border-primary-700 border ${className}`}\n      style={{\n        minHeight: \"198px\",\n        maxHeight: \"50vh\",\n        top: \"-10px\",\n        left: \"-10px\",\n        right: \"0px\",\n        boxShadow: \"-3px 4px 14px rgba(0, 0, 0, 0.7)\",\n        width: \"calc(100% + 20px)\",\n        zIndex: -1,\n      }}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n});\n\nSearchOverlay.displayName = \"SearchOverlay\";\n"
  },
  {
    "path": "kibbeh/src/ui/Search/SearchResult/RoomSearchResult.tsx",
    "content": "import { Room } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { BubbleText } from \"../../BubbleText\";\nimport { formatNumber } from \"../../RoomCard\";\n\nexport interface RoomSearchResultProps {\n  room: Room;\n  className?: string;\n  onClick?: () => void;\n}\n\nexport const RoomSearchResult: React.FC<RoomSearchResultProps> = ({\n  room,\n  className = \"\",\n  onClick = () => undefined,\n}) => {\n  return (\n    <div\n      className={`flex cursor-pointer hover:bg-primary-700 px-4 py-3 w-full rounded-8 ${className}`}\n      onClick={onClick}\n    >\n      <div className=\"flex flex-col w-full\">\n        <div className=\"flex w-full\">\n          <span className=\"text-primary-100 font-bold flex-1 items-center\">\n            {room.name}\n          </span>\n          <BubbleText live>{formatNumber(room.numPeopleInside)}</BubbleText>\n        </div>\n        {/* <span className=\"text-primary-300\">\n          {room.hosts\n            .slice(0, 3)\n            .map((x) => x.displayName)\n            .join(\", \")}\n        </span> */}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Search/SearchResult/UserSearchResult.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { SingleUser } from \"../../UserAvatar\";\n\nexport interface UserSearchResultProps {\n  user: User;\n  className?: string;\n  onClick?: () => void;\n}\n\nexport const UserSearchResult: React.FC<UserSearchResultProps> = ({\n  user,\n  className = \"\",\n  onClick = () => undefined,\n}) => {\n  return (\n    <div\n      className={`flex cursor-pointer hover:bg-primary-700 px-4 py-3 w-full rounded-8 ${className}`}\n      onClick={onClick}\n    >\n      <div className=\"flex mr-3\">\n        <SingleUser isOnline={user.online} src={user.avatarUrl} size=\"md\" />\n      </div>\n      <div className=\"flex flex-col\">\n        <span className=\"text-primary-100 font-bold\">{user.displayName}</span>\n        <span className=\"text-primary-300\">@{user.username}</span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Search/SearchResult/index.ts",
    "content": "import { UserSearchResultProps } from \"./UserSearchResult\";\nimport { RoomSearchResultProps } from \"./RoomSearchResult\";\n\nexport { UserSearchResult } from \"./UserSearchResult\";\nexport { RoomSearchResult } from \"./RoomSearchResult\";\n\nexport type { UserSearchResultProps, RoomSearchResultProps };\n"
  },
  {
    "path": "kibbeh/src/ui/SettingsDropdown.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport isElectron from \"is-electron\";\nimport { useRouter } from \"next/router\";\nimport React, { ReactNode, useState } from \"react\";\nimport { useDebugAudioStore } from \"../global-stores/useDebugAudio\";\nimport {\n  DeveloperIcon,\n  OutlineGlobe,\n  SolidBug,\n  SolidCaretRight,\n  SolidUser,\n  SolidVolume,\n} from \"../icons\";\nimport SvgSolidDiscord from \"../icons/SolidDiscord\";\nimport SvgSolidDownload from \"../icons/SolidDownload\";\nimport { ApiPreloadLink } from \"../shared-components/ApiPreloadLink\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { BaseOverlay } from \"../ui/BaseOverlay\";\nimport { SettingsIcon } from \"../ui/SettingsIcon\";\nimport { LanguageSelector } from \"./LanguageSelector\";\n\nexport const SettingsDropdown: React.FC<{\n  user: User;\n  onCloseDropdown: () => void;\n  onActionButtonClicked: () => void;\n}> = ({ user, onCloseDropdown, onActionButtonClicked }) => {\n  const [currentOverlay, setCurrentOverlay] = useState<ReactNode>(null);\n  const { t } = useTypeSafeTranslation();\n  const { debugAudio, setDebugAudio } = useDebugAudioStore();\n\n  const { push } = useRouter();\n\n  return (\n    <div\n      className=\"flex whitespace-nowrap overflow-ellipsis\"\n      style={{ width: 200 }}\n    >\n      <BaseOverlay\n        onActionButtonClicked={onActionButtonClicked}\n        actionButton={t(\"components.settingsDropdown.logOut.button\")}\n        overlay={currentOverlay}\n      >\n        <div className=\"flex flex-col\">\n          <ApiPreloadLink\n            data-testid=\"profile-link\"\n            route=\"profile\"\n            data={{ username: user.username }}\n          >\n            <SettingsIcon\n              onClick={onCloseDropdown}\n              icon={<SolidUser />}\n              label={t(\"components.settingsDropdown.profile\")}\n              transition\n            />\n          </ApiPreloadLink>\n          {/* <SettingsIcon icon={<SolidSettings />} label={\"Settings\"} />\n        <SettingsIcon icon={<SolidDogenitro />} label={\"Wallet\"} /> */}\n          <SettingsIcon\n            icon={<OutlineGlobe />}\n            label={t(\"components.settingsDropdown.language\")}\n            trailingIcon={<SolidCaretRight />}\n            transition\n            onClick={() =>\n              setCurrentOverlay(\n                <LanguageSelector onClose={() => setCurrentOverlay(null)} />\n              )\n            }\n          />\n          {/* <SettingsIcon icon={<SolidHelp />} label={\"Help\"} /> */}\n          <a\n            href=\"https://github.com/benawad/dogehouse/issues\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n          >\n            <SettingsIcon\n              onClick={onCloseDropdown}\n              icon={<SolidBug />}\n              label={t(\"components.settingsDropdown.reportABug\")}\n              transition\n            />\n          </a>\n          <SettingsIcon\n            label={\n              !debugAudio\n                ? t(\"components.settingsDropdown.debugAudio.debugAudio\")\n                : t(\"components.settingsDropdown.debugAudio.stopDebugger\")\n            }\n            icon={<SolidVolume />}\n            transition\n            onClick={() => setDebugAudio(!debugAudio)}\n          />\n\n          {!isElectron() ? (\n            <SettingsIcon\n              onClick={() => push(\"/download\")}\n              icon={<SvgSolidDownload />}\n              label={t(\"components.settingsDropdown.downloadApp\")}\n              transition\n            />\n          ) : null}\n\n          <SettingsIcon\n            onClick={() => push(\"/developer/bots\")}\n            icon={<DeveloperIcon />}\n            label={t(\"components.settingsDropdown.developer\")}\n            transition\n          />\n\n          <a\n            href=\"https://discord.gg/wCbKBZF9cV\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n          >\n            <SettingsIcon\n              onClick={onCloseDropdown}\n              icon={<SvgSolidDiscord />}\n              label={\"Discord\"}\n              transition\n            />\n          </a>\n        </div>\n      </BaseOverlay>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/SettingsIcon.tsx",
    "content": "import React, { LegacyRef, ReactElement } from \"react\";\n\nexport interface SettingsIconProps {\n  a?: {\n    href: string;\n    download?: string;\n    ref?: LegacyRef<HTMLAnchorElement>;\n  };\n  icon?: ReactElement;\n  label?: string;\n  trailingIcon?: ReactElement;\n  classes?: string;\n  transition?: boolean;\n  onClick?: () => void;\n  last?: boolean;\n}\n\nexport const SettingsIcon: React.FC<SettingsIconProps> = ({\n  a,\n  icon,\n  label,\n  trailingIcon,\n  classes = \"\",\n  transition,\n  onClick,\n  last,\n}) => {\n  const cn = `\n      flex w-full items-center px-4 py-4 md:py-2 cursor-pointer md:hover:bg-primary-700\n       md:border-none ${last ? \"\" : \"border-b\"} border-primary-700 ${\n    transition ? `transition duration-200 ease-out` : ``\n  } ${classes}`;\n\n  if (a) {\n    return (\n      <a\n        ref={a.ref}\n        href={a.href}\n        download={a.download}\n        onClick={onClick}\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className={`${cn} text-primary-100`}\n      >\n        {label}\n      </a>\n    );\n  }\n\n  return (\n    <button onClick={onClick} className={cn}>\n      {icon}\n      <span className=\"text-lg md:text-base flex md:ml-2 ml-4 text-primary-100 flex-1\">\n        {label}\n      </span>\n      {trailingIcon ? trailingIcon : null}\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/SettingsItemButton.tsx",
    "content": "import React from \"react\";\nimport { BaseSettingsItem } from \"./BaseSettingsItem\";\nimport { Button } from \"./Button\";\n\nexport type SettingsItemButtonProps = {\n  text: string;\n  buttonText: string;\n  disabled?: boolean;\n  className?: string;\n  onClick: (e: React.MouseEvent | React.TouchEvent) => void;\n};\n\nexport const SettingsItemButton: React.FC<SettingsItemButtonProps> = ({\n  children,\n  buttonText,\n  disabled = false,\n  className = \"\",\n  onClick,\n}) => {\n  return (\n    <BaseSettingsItem className={`p-4 ${className}`}>\n      <div className=\"text-primary-100 mb-4\">{children}</div>\n      <Button\n        color=\"accent-secondary\"\n        size=\"small\"\n        onClick={onClick}\n        disabled={disabled}\n        className=\"py-2\"\n      >\n        {buttonText}\n      </Button>\n    </BaseSettingsItem>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/SettingsWrapper.tsx",
    "content": "import React, { ReactNode } from \"react\";\n\nexport interface SettingsWrapperProps {\n  children: ReactNode;\n}\n\nexport const SettingsWrapper: React.FC<SettingsWrapperProps> = ({\n  children,\n  ...props\n}) => {\n  return (\n    <div className=\"bg-primary-800 rounded-8 p-4\" {...props}>\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Spinner.tsx",
    "content": "import React from \"react\";\n\n// I'm not doing string interpolation so tailwind can purge the css\nconst sizes = {\n  \"2\": \"h-2 w-2\",\n  \"4\": \"h-4 w-4\",\n};\n\nexport const Spinner: React.FC<{ size?: keyof typeof sizes }> = ({\n  size = \"4\",\n}) => {\n  return (\n    <svg\n      className={`animate-spin text-button ${sizes[size]}`}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      fill=\"none\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path\n        fill=\"currentColor\"\n        d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n      />\n    </svg>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Tag.tsx",
    "content": "import React from \"react\";\n\ninterface TagProps {\n  glow?: boolean;\n  className?: string;\n}\n\n// @todo the tag doesn't really glow like in figma right now\nexport const Tag: React.FC<TagProps> = ({ children, glow, className = \"\" }) => {\n  return (\n    <div\n      className={`cursor-pointer bg-primary-700 hover:bg-primary-600 text-sm px-2 font-bold text-primary-100 justify-center items-center rounded flex justify-center items-center ${\n        glow ? `border` : ``\n      } ${className}`}\n      style={{\n        height: \"22px\",\n        boxShadow: glow ? \"0px 0px 7px var(--color-accent-glow)\" : \"\",\n        border: glow ? \".5px solid var(--color-accent)\" : \"\",\n      }}\n    >\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Toast.tsx",
    "content": "import * as React from \"react\";\nimport { SolidPlus } from \"../icons\";\n\nexport type ToastDurations = \"default\" | \"sticky\";\n\nexport interface ErrorMessageProps {\n  message: string;\n  button?: React.ReactNode;\n  duration?: ToastDurations;\n  onClose?: () => void;\n}\n\nexport const ErrorToast: React.FC<ErrorMessageProps> = ({\n  message,\n  button,\n  duration = \"default\",\n  onClose,\n}) => {\n  const onCloseRef = React.useRef(onClose);\n  onCloseRef.current = onClose;\n  React.useEffect(() => {\n    if (duration === \"sticky\") {\n      return;\n    }\n\n    const timer = setTimeout(() => {\n      onCloseRef.current?.();\n    }, 7000);\n\n    return () => {\n      clearTimeout(timer);\n    };\n  }, [duration]);\n\n  return (\n    <div\n      className={`flex rounded-8 p-3 relative w-full items-center justify-center text-button transition-transform duration-300 bg-secondary`}\n      data-testid=\"error-message\"\n    >\n      {onClose ? (\n        <div\n          className={`flex absolute cursor-pointer`}\n          style={{\n            top: 5,\n            right: 7,\n            width: 13,\n            height: 13,\n          }}\n          onClick={onClose}\n          data-testid=\"close-btn\"\n        >\n          <SolidPlus style={{ transform: \"rotate(45deg)\" }} />\n        </div>\n      ) : null}\n      <div className={`flex space-x-4 items-center`}>\n        <div className={`bold`}>{message}</div>\n        {button}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/Twemoji.tsx",
    "content": "import React, { ReactElement } from \"react\";\nimport Grapheme from \"grapheme-splitter\";\nimport { parse } from \"twemoji-parser\";\nimport eRegex from \"emoji-regex\";\n\ninterface TwemojiProps extends React.ImgHTMLAttributes<HTMLImageElement> {\n  text: string;\n  className?: string;\n}\n\nconst splitter = new Grapheme();\n\nexport const ParseTextToTwemoji: React.FC<TwemojiProps> = ({\n  text,\n  className = \"\",\n  ...props\n}) => {\n  const regex = eRegex();\n  const chars = splitter.splitGraphemes(text);\n\n  return (\n    <>\n      {chars.map((e, i) =>\n        eRegex().test(e) ? (\n          <img\n            {...props}\n            key={i}\n            className={`emoji ${className || \"\"}`}\n            src={parse(e)[0].url}\n            alt={parse(e)[0].text}\n          />\n        ) : (\n          <React.Fragment key={i}>{e}</React.Fragment>\n        )\n      )}\n    </>\n  );\n};\n\nconst twemojiMap = {\n  \"📣\": \"1f4e3\",\n};\n\ninterface StaticTwemojiProps extends React.ImgHTMLAttributes<HTMLImageElement> {\n  emoji: keyof typeof twemojiMap;\n  className?: string;\n}\n\nexport const StaticTwemoji: React.FC<StaticTwemojiProps> = ({\n  emoji,\n  className = \"\",\n  ...props\n}) => {\n  return (\n    <>\n      <img\n        {...props}\n        className={`emoji ${className || \"\"}`}\n        src={`https://twemoji.maxcdn.com/v/latest/svg/${twemojiMap[emoji]}.svg`}\n      />\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UpcomingRoomCardLg.tsx",
    "content": "import React, { useState, useRef } from \"react\";\nimport { BaseUser } from \"@dogehouse/kebab\";\nimport { format } from \"date-fns\";\n\nimport { Tag } from \"./Tag\";\nimport { Button } from \"./Button\";\nimport { MultipleUsers } from \"./UserAvatar\";\nimport { BaseDropdownSm, BaseDropdownSmItem } from \"./BaseDropdownSm\";\nimport {\n  LinkIcon,\n  ShareIcon,\n  TwitterIcon,\n  SolidCalendar,\n  SolidInstagram,\n} from \"../icons\";\nimport { useOnClickOutside } from \"../shared-hooks/useOnClickOutside\";\n\nexport interface UpcomingRoomCardLgProps {\n  title: string;\n  date: number;\n  tags: string[];\n  hosts: BaseUser[];\n  descriptions: string;\n}\n\ninterface DropdownProps {}\n\nconst calenderDropdownItems = [\n  { key: 0, value: \"Apple Calender\" },\n  { key: 1, value: \"Google\" },\n  { key: 2, value: \"Outlook\" },\n  { key: 3, value: \"Outlook Web App\" },\n  { key: 4, value: \"Yahoo\" },\n];\n\nconst shareDropdownItems = [\n  { key: 0, value: \"Copy link\", icon: LinkIcon },\n  { key: 1, value: \"Twitter\", icon: TwitterIcon },\n  { key: 2, value: \"Instagram Post\", icon: SolidInstagram },\n  { key: 3, value: \"Instagram Stroy\", icon: SolidInstagram },\n];\n\nconst CalenderDropDown: React.FC<DropdownProps> = () => {\n  const ref = useRef(null);\n  const [open, setOpen] = useState(false);\n  const clickHandler = (value: number) => {\n    setOpen(false);\n    console.log(value);\n  };\n\n  return (\n    <div ref={ref} className=\"relative\">\n      <Button\n        size=\"small\"\n        color=\"secondary\"\n        className=\"mr-2 whitespace-nowrap\"\n        onClick={() => setOpen((v) => !v)}\n      >\n        <span style={{ marginRight: \"8px\" }}>\n          <SolidCalendar />\n        </span>\n        Add to calendar\n      </Button>\n      {open && (\n        <span className=\"absolute top-5 left-0\">\n          <BaseDropdownSm>\n            {calenderDropdownItems.map(({ key, value }, i) => (\n              <BaseDropdownSmItem\n                key={i}\n                className=\"whitespace-nowrap\"\n                onClick={() => clickHandler(key)}\n              >\n                {value}\n              </BaseDropdownSmItem>\n            ))}\n          </BaseDropdownSm>\n        </span>\n      )}\n    </div>\n  );\n};\n\nconst ShareDropDown: React.FC<DropdownProps> = () => {\n  const ref = useRef(null);\n  const [open, setOpen] = useState(false);\n  const clickHandler = (value: number) => {\n    setOpen(false);\n    console.log(value);\n  };\n\n  useOnClickOutside(ref, () => setOpen(false));\n\n  return (\n    <div ref={ref} className=\"relative\">\n      <Button size=\"small\" color=\"secondary\" onClick={() => setOpen((v) => !v)}>\n        <ShareIcon />\n      </Button>\n      {open && (\n        <span className=\"absolute top-5 right-0\">\n          <BaseDropdownSm>\n            {shareDropdownItems.map(({ key, value, icon: Icon }, i) => (\n              <BaseDropdownSmItem\n                key={i}\n                className=\"flex items-center whitespace-nowrap\"\n                onClick={() => clickHandler(key)}\n              >\n                <span className=\"mr-2\">\n                  <Icon />\n                </span>\n                {value}\n              </BaseDropdownSmItem>\n            ))}\n          </BaseDropdownSm>\n        </span>\n      )}\n    </div>\n  );\n};\n\nexport const UpcomingRoomCardLg: React.FC<UpcomingRoomCardLgProps> = ({\n  title,\n  hosts,\n  date,\n  tags,\n  descriptions,\n}) => {\n  const visibleHosts = hosts.slice(0, 3);\n\n  return (\n    <div className=\"p-4 bg-primary-800 rounded-8\">\n      <div className=\"flex justify-between mb-4\">\n        <h6 className=\"text-primary-100 font-bold overflow-ellipsis overflow-hidden whitespace-nowrap mr-5\">\n          {title}\n        </h6>\n        <div className=\"flex\">\n          <CalenderDropDown />\n          <ShareDropDown />\n        </div>\n      </div>\n      <div className=\"flex mb-4\">\n        <MultipleUsers srcArray={visibleHosts.map((h) => h.avatarUrl)} />\n        <div className=\"text-primary-300 ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap\">\n          {visibleHosts.map((h) => h.displayName).join(\", \")}\n        </div>\n      </div>\n      <div>\n        <span className=\"text-accent\">\n          {format(+date, \"MM/dd/yyyy hh:mma\")}\n        </span>\n        <span className=\"text-primary-300 font-medium\"> | {descriptions}</span>\n      </div>\n      {tags.length && (\n        <div className=\"flex flex-wrap mt-2\">\n          {tags.map((t, index) => (\n            <Tag key={index} className=\"mr-1 mt-2\">\n              {t}\n            </Tag>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UpcomingRoomsCard.tsx",
    "content": "import { format, isToday, isTomorrow } from \"date-fns\";\nimport Link from \"next/link\";\nimport React from \"react\";\nimport { SolidPlus } from \"../icons\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { BoxedIcon } from \"./BoxedIcon\";\nimport { RoomCardHeading } from \"./RoomCardHeading\";\nimport { MultipleUsers } from \"./UserAvatar\";\n\ninterface FormattedDateProps {\n  scheduledFor: Date;\n}\n\nconst FormattedDate: React.FC<FormattedDateProps> = ({ scheduledFor }) => {\n  const { t } = useTypeSafeTranslation();\n  let text = \"\";\n  if (isToday(scheduledFor)) {\n    text = format(scheduledFor, `h:mm a`);\n  } else {\n    text = format(scheduledFor, `do MMM, h:mm a`);\n  }\n  return <>{text}</>;\n};\n\nexport interface UserCardProps {\n  avatars: string[];\n  speakers: string[];\n}\n\nexport interface ScheduledRoomSummaryCardProps {\n  onClick: () => void;\n  id: string;\n  scheduledFor: Date;\n  speakersInfo: UserCardProps;\n  title: string;\n  transition?: boolean;\n}\n\nexport interface UpcomingRoomsCardProps {\n  onCreateScheduledRoom: () => void;\n  rooms: ScheduledRoomSummaryCardProps[];\n}\n\nconst UserCard: React.FC<UserCardProps> = ({ avatars, speakers }) => {\n  return (\n    <div className=\"w-full flex items-center\">\n      <MultipleUsers srcArray={avatars} />\n      <div className=\"flex ml-1 text-primary-300 text-sm\">\n        {speakers.join(\", \")}\n      </div>\n    </div>\n  );\n};\n\nexport const ScheduledRoomSummaryCard: React.FC<ScheduledRoomSummaryCardProps> = ({\n  onClick,\n  scheduledFor,\n  speakersInfo,\n  title,\n  transition,\n}) => {\n  return (\n    <button\n      onClick={onClick}\n      className={`px-4 py-2 w-full bg-primary-800 flex flex-col gap-2 border-b border-primary-600 cursor-pointer last:border-b-0 ${transition ? `transition duration-200 ease-in-out` : ``\n        } hover:bg-primary-700 z-0`}\n    >\n      <div className=\"flex text-accent text-sm uppercase\">\n        <FormattedDate scheduledFor={scheduledFor} />\n      </div>\n      <RoomCardHeading text={title} />\n      <UserCard {...speakersInfo} />\n    </button>\n  );\n};\n\nexport const UpcomingRoomsCard: React.FC<UpcomingRoomsCardProps> = ({\n  onCreateScheduledRoom,\n  rooms,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <div className=\"w-full rounded-lg overflow-y-auto flex flex-col\">\n      <div className=\"px-4 py-3 bg-primary-800 border-b border-primary-600 flex justify-between items-center\">\n        <h4 className=\"text-primary-100 font-bold\">\n          {t(\"components.upcomingRoomsCard.upcomingRooms\")}\n        </h4>\n        <BoxedIcon\n          data-testid=\"create-scheduled-room\"\n          onClick={onCreateScheduledRoom}\n          style={{ height: \"26px\", width: \"26px\" }}\n          transition\n        >\n          <SolidPlus width={12} height={12} />\n        </BoxedIcon>\n      </div>\n      <div className=\"flex flex-col\">\n        {rooms.map((room) => (\n          <ScheduledRoomSummaryCard transition key={room.id} {...room} />\n        ))}\n      </div>\n\n      <Link href=\"/scheduled-rooms\">\n        <a\n          className=\"px-4 py-3 text-primary-100 font-bold bg-primary-700\"\n          data-testid=\"view-scheduled-rooms\"\n        >\n          {t(\"components.upcomingRoomsCard.exploreMoreRooms\")}\n        </a>\n      </Link>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/MultipleUsers.tsx",
    "content": "import React from \"react\";\n\nimport { SingleUser } from \"./SingleUser\";\n\nexport interface AvatarProps {\n  srcArray: string[];\n  className?: string;\n}\n\nexport const MultipleUsers: React.FC<AvatarProps> = ({\n  srcArray,\n  className = \"\",\n}) => {\n  return (\n    <div className={`flex ${className}`}>\n      {srcArray.slice(0, 3).map((s, i) => (\n        <span\n          key={s + i}\n          className=\"rounded-full bg-primary-800 border-primary-800 shadow-outlineSm\"\n          style={{\n            zIndex: srcArray.length - i,\n            marginLeft: i > 0 ? -5 : 0,\n            height: 20,\n            width: 20,\n            overflow: \"hidden\",\n          }}\n        >\n          <SingleUser src={s} size=\"xs\" />\n        </span>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/SingleUser.tsx",
    "content": "import React, { useState } from \"react\";\nimport { SolidDeafenedOff, SolidMicrophoneOff, BotIcon } from \"../../icons\";\nimport SolidVolumeOff from \"../../icons/SolidVolumeOff\";\n\nexport const avatarSizeMap = {\n  default: \"80px\",\n  lg: \"60px\",\n  md: \"50px\",\n  sm: \"40px\",\n  xs: \"20px\",\n  xxs: \"30px\",\n};\n\nexport const onlineIndicatorStyleMap = {\n  default: {\n    width: \"15px\",\n    height: \"15px\",\n    right: \"2px\",\n    bottom: \"-4px\",\n    borderWidth: \"4px\",\n  },\n  lg: {\n    width: \"12px\",\n    height: \"12px\",\n    right: \"2px\",\n    bottom: \"-2px\",\n    borderWidth: \"2px\",\n  },\n  md: {\n    width: \"10px\",\n    height: \"10px\",\n    right: \"2px\",\n    bottom: \"-2px\",\n    borderWidth: \"2px\",\n  },\n  sm: {\n    width: \"8px\",\n    height: \"8px\",\n    right: \"2px\",\n    bottom: \"-2px\",\n    borderWidth: \"2px\",\n  },\n  xs: {\n    width: \"4px\",\n    height: \"4px\",\n    right: \"0px\",\n    bottom: \"-1px\",\n    borderWidth: \"1px\",\n  },\n  xxs: {\n    width: \"6px\",\n    height: \"6px\",\n    right: \"1px\",\n    bottom: \"-1px\",\n    borderWidth: \"1px\",\n  },\n};\n\nexport interface AvatarProps {\n  src: string;\n  size?: keyof typeof onlineIndicatorStyleMap;\n  className?: string;\n  isOnline?: boolean;\n  muted?: boolean;\n  deafened?: boolean;\n  activeSpeaker?: boolean;\n  username?: string;\n  hover?: boolean;\n  isBot?: boolean;\n}\n\nexport const SingleUser: React.FC<AvatarProps> = ({\n  src,\n  size = \"default\",\n  className = \"\",\n  isOnline = false,\n  hover = false,\n  muted,\n  deafened,\n  activeSpeaker,\n  username,\n  isBot,\n}) => {\n  const [isError, setError] = useState(false);\n  const sizeStyle = onlineIndicatorStyleMap[size];\n  return (\n    <div\n      className={`relative inline-block ${className}`}\n      style={{\n        width: avatarSizeMap[size],\n        height: avatarSizeMap[size],\n      }}\n      data-testid=\"single-user-avatar\"\n    >\n      <img\n        alt={username ? `${username}-s-avatar` : \"your-avatar\"}\n        style={{\n          boxShadow: activeSpeaker ? \"0 0 0 2px var(--color-accent)\" : \"\",\n        }}\n        className={`rounded-full w-full h-full object-cover ${\n          deafened ? \"opacity-60\" : \"\"\n        }`}\n        onError={() => setError(true)}\n        src={\n          isError\n            ? `https://ui-avatars.com/api/${\n                username ? `&name=${username}` : \"&name\"\n              }&rounded=true&background=B23439&bold=true&color=FFFFFF`\n            : src\n        }\n      />\n      {hover && (\n        <div\n          className={`bg-primary-900 hover:opacity-20 transition duration-200 opacity-0 absolute w-full h-full top-0 left-0 rounded-full`}\n        ></div>\n      )}\n      {isOnline && (\n        <span\n          className={\n            \"rounded-full absolute box-content bg-accent border-primary-800\"\n          }\n          style={sizeStyle}\n          data-testid=\"online-indictor\"\n        ></span>\n      )}\n      {/* {isBot && (\n        <span\n          className={\n            \"rounded-full absolute box-content bg-primary-800 border-primary-800 text-secondary items-center justify-center\"\n          }\n          style={{ ...sizeStyle, padding: 2, top: -2 }}\n          data-testid=\"online-indictor\"\n        >\n          <BotIcon\n            data-testid={`bot:${username}`}\n            width={sizeStyle.width}\n            height={sizeStyle.width}\n          />\n        </span>\n      )} */}\n      {muted && (\n        <span\n          className={\n            \"rounded-full absolute box-content bg-primary-800 border-primary-800 text-accent items-center justify-center\"\n          }\n          style={{ ...sizeStyle, padding: 2 }}\n          data-testid=\"online-indictor\"\n        >\n          <SolidMicrophoneOff\n            data-testid={`muted:${username}`}\n            width={sizeStyle.width}\n            height={sizeStyle.width}\n          />\n        </span>\n      )}\n      {deafened && (\n        <span\n          className={\n            \"rounded-full absolute box-content bg-primary-800 border-primary-800 text-accent items-center justify-center\"\n          }\n          style={{ ...sizeStyle, padding: 2 }}\n          data-testid=\"online-indictor\"\n        >\n          <SolidDeafenedOff\n            data-testid={`deafened:${username}`}\n            width={sizeStyle.width}\n            height={sizeStyle.width}\n          />\n        </span>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/index.ts",
    "content": "export { SingleUser } from \"./SingleUser\";\nexport { MultipleUsers } from \"./MultipleUsers\";\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/tests/MultipleUser.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../../test-utils\";\nimport { MultipleUsers } from \"../\";\n\ndescribe(\"UserAvatar\", () => {\n  describe(\"MultipleUsers\", () => {\n    it(\"should render 3 single users max.\", () => {\n      const srcArray = [\"\", \"\", \"\", \"\"];\n      const { getAllByTestId } = render(<MultipleUsers srcArray={srcArray} />);\n      const avatar = getAllByTestId(\"single-user-avatar\");\n\n      expect(avatar).toHaveLength(3);\n    });\n\n    it(\"should render 2 single users\", () => {\n      const srcArray = [\"\", \"\"];\n      const { getAllByTestId } = render(<MultipleUsers srcArray={srcArray} />);\n      const avatar = getAllByTestId(\"single-user-avatar\");\n\n      expect(avatar).toHaveLength(2);\n    });\n\n    it(\"should match snapshot\", () => {\n      const srcArray = [\"\", \"\", \"\", \"\"];\n\n      const { getAllByTestId } = render(<MultipleUsers srcArray={srcArray} />);\n      const avatar = getAllByTestId(\"single-user-avatar\");\n\n      expect(avatar).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/tests/SingleUser.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../../test-utils\";\nimport { SingleUser } from \"../\";\n\ndescribe(\"UserAvatar\", () => {\n  describe(\"SingleUser\", () => {\n    const src = \"\";\n    it(\"should render correctly\", () => {\n      const { getByTestId } = render(<SingleUser src={src} size=\"default\" />);\n      const avatar = getByTestId(\"single-user-avatar\");\n\n      expect(avatar).toBeVisible();\n    });\n\n    it(\"should show the online indicator is the isOnline prop is ture\", () => {\n      const { getByTestId } = render(\n        <SingleUser src={src} size=\"default\" isOnline />\n      );\n      const onlineIndicator = getByTestId(\"online-indictor\");\n\n      expect(onlineIndicator).toBeVisible();\n    });\n\n    it(\"should match snapshot\", () => {\n      const { getByTestId } = render(<SingleUser src={src} size=\"default\" />);\n      const avatar = getByTestId(\"single-user-avatar\");\n\n      expect(avatar).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/tests/__snapshots__/MultipleUser.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`UserAvatar MultipleUsers should match snapshot 1`] = `\nArray [\n  <div\n    class=\"relative inline-block \"\n    data-testid=\"single-user-avatar\"\n    style=\"width: 20px; height: 20px;\"\n  >\n    <img\n      alt=\"your-avatar\"\n      class=\"rounded-full w-full h-full object-cover \"\n      src=\"\"\n    />\n  </div>,\n  <div\n    class=\"relative inline-block \"\n    data-testid=\"single-user-avatar\"\n    style=\"width: 20px; height: 20px;\"\n  >\n    <img\n      alt=\"your-avatar\"\n      class=\"rounded-full w-full h-full object-cover \"\n      src=\"\"\n    />\n  </div>,\n  <div\n    class=\"relative inline-block \"\n    data-testid=\"single-user-avatar\"\n    style=\"width: 20px; height: 20px;\"\n  >\n    <img\n      alt=\"your-avatar\"\n      class=\"rounded-full w-full h-full object-cover \"\n      src=\"\"\n    />\n  </div>,\n]\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/UserAvatar/tests/__snapshots__/SingleUser.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`UserAvatar SingleUser should match snapshot 1`] = `\n<div\n  class=\"relative inline-block \"\n  data-testid=\"single-user-avatar\"\n  style=\"width: 80px; height: 80px;\"\n>\n  <img\n    alt=\"your-avatar\"\n    class=\"rounded-full w-full h-full object-cover \"\n    src=\"\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/UserBadge.tsx",
    "content": "import React, { HTMLAttributes } from \"react\";\n\nconst badgeVariants = {\n  \"primary-700\": \"bg-primary-700\",\n  primary: \"bg-primary-600\",\n  secondary: \"bg-accent\",\n};\n\nconst colorVariants = {\n  white: \"text-primary-100\",\n  grey: \"text-primary-300\",\n};\n\ninterface UserBadgeProps {\n  variant?: keyof typeof badgeVariants;\n  color?: keyof typeof colorVariants;\n  className?: string;\n  title?: string;\n  naked?: boolean;\n}\n\nexport const UserBadge: React.FC<UserBadgeProps> = ({\n  children,\n  color = \"white\",\n  variant = \"primary-700\",\n  className = \"\",\n  title = \"\",\n  naked = false,\n}) => {\n  return (\n    <>\n      {naked ? (\n        <div className=\"mr-1 select-none\" title={title}>\n          {children}\n        </div>\n      ) : (\n        <div\n          title={title}\n          className={`flex ${badgeVariants[variant]} select-none text-xs px-1 font-bold ${colorVariants[color]} justify-center items-center mr-1 rounded ${className}`}\n          style={{ height: \"16px\", minWidth: \"31px\", width: \"max-content\" }}\n        >\n          {children}\n        </div>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserBadgeLg.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport {\n  SolidContributor,\n  SolidDogenitro,\n  SolidStaff,\n  LogoIcon,\n} from \"../icons\";\nimport { ContributorBadge, StaffBadge } from \"../icons/badges\";\n\nexport interface UserBadgeLgProps extends React.HTMLProps<HTMLDivElement> {\n  icon: \"logo\" | \"dogeNitro\" | \"dogeStaff\" | \"dogeContributor\";\n  contributions?: number;\n}\n\nexport const UserBadgeLg: React.FC<UserBadgeLgProps> = ({\n  icon,\n  children,\n  contributions,\n}) => {\n  const [parsedIcon, setParsedIcon] = useState(null as React.ReactNode);\n  useEffect(() => {\n    switch (icon) {\n      case \"logo\":\n        setParsedIcon(\n          <LogoIcon\n            fillCurrent={true}\n            className=\"relative transform translate-x-n1/2 translate-y-n1/2 top-1/2 left-1/2\"\n          />\n        );\n        break;\n      case \"dogeNitro\":\n        setParsedIcon(\n          <SolidDogenitro className=\"relative transform translate-x-n1/2 translate-y-n1/2 top-1/2 left-1/2\" />\n        );\n        break;\n      case \"dogeStaff\":\n        setParsedIcon(\n          <StaffBadge\n            style={{ color: \"var(--color-primary-300)\" }}\n            className=\"relative transform translate-x-n1/2 translate-y-n1/2 top-1/2 left-1/2\"\n          />\n        );\n        break;\n      case \"dogeContributor\":\n        setParsedIcon(\n          <ContributorBadge\n            contributions={contributions!}\n            style={{ color: \"var(--color-primary-300)\" }}\n            className=\"relative transform translate-x-n1/2 translate-y-n1/2 top-1/2 left-1/2\"\n          />\n        );\n        break;\n    }\n  }, [setParsedIcon, icon, contributions]);\n\n  return (\n    <div className=\"flex text-primary-300\">\n      {parsedIcon ? (\n        <span style={{ width: 16 }} className=\"mr-2\">\n          {parsedIcon}\n        </span>\n      ) : (\n        \"\"\n      )}\n      <span>\n        {children}\n        {contributions ? ` (${contributions})` : \"\"}\n      </span>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserProfile.tsx",
    "content": "import React from \"react\";\nimport { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport { ProfileHeader } from \"./ProfileHeader\";\nimport { ProfileAbout } from \"./ProfileAbout\";\nimport { ProfileTabs } from \"./ProfileTabs\";\nimport { badge } from \"./UserSummaryCard\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { UserBadgeLgProps } from \"./UserBadgeLg\";\nimport { ContributorBadge, StaffBadge } from \"../icons/badges\";\n\ninterface UserProfileProps {\n  user: UserWithFollowInfo;\n  isCurrentUser?: boolean;\n}\n\nexport const UserProfile: React.FC<UserProfileProps> = ({\n  user,\n  isCurrentUser,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  const badges: badge[] = [];\n  const tags: UserBadgeLgProps[] = [];\n\n  if (user.staff) {\n    badges.push({\n      content: <StaffBadge />,\n      variant: \"primary\",\n      color: \"white\",\n      title: t(\"components.userBadges.dhStaff\"),\n      naked: true,\n    });\n    tags.push({\n      icon: \"dogeStaff\",\n      children: t(\"components.userBadges.dhStaff\"),\n    });\n  }\n\n  if (user.contributions > 0) {\n    badges.push({\n      content: <ContributorBadge contributions={user.contributions} />,\n      variant: \"primary\",\n      color: \"white\",\n      title: `${t(\"components.userBadges.dhContributor\")} (${user.contributions} ${t(\"pages.admin.contributions\")})`,\n      naked: true,\n    });\n    tags.push({\n      icon: \"dogeContributor\",\n      contributions: user.contributions,\n      children: t(\"components.userBadges.dhContributor\"),\n    });\n  }\n\n  if (user.botOwnerId) {\n    badges.push({\n      content: t(\"pages.viewUser.bot\"),\n      variant: \"primary\",\n      color: \"white\",\n      title: t(\"pages.viewUser.bot\"),\n    });\n  }\n  return (\n    <>\n      <ProfileHeader\n        user={user}\n        pfp={user.avatarUrl}\n        displayName={user.displayName}\n        isCurrentUser={isCurrentUser}\n        username={user.username}\n        badges={badges}\n      />\n      <ProfileTabs user={user} className=\"mt-4\" aboutTags={tags} />\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserSummaryCard.tsx",
    "content": "import React from \"react\";\n\nimport { SingleUser } from \"./UserAvatar\";\nimport { UserBadge } from \"./UserBadge\";\nimport { kFormatter } from \"../lib/kFormatter\";\nimport { ApiPreloadLink } from \"../shared-components/ApiPreloadLink\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\n\nexport type badge = {\n  color: \"white\" | \"grey\";\n  content: React.ReactNode;\n  variant: \"primary\" | \"secondary\" | \"primary-700\";\n  classname?: string;\n  title?: string;\n  naked?: boolean;\n};\n\nexport interface UserSummaryCardProps {\n  onClick: () => void;\n  id: string;\n  displayName: string;\n  username: string;\n  numFollowers: number;\n  numFollowing: number;\n  isOnline: boolean;\n  avatarUrl: string;\n  badges: badge[];\n  bio?: string | null;\n  website?: string;\n}\n\ninterface BadgesProps {\n  badges: badge[];\n}\n\ninterface WebsiteProps {\n  website: string;\n}\n\nexport const Badges: React.FC<BadgesProps> = ({ badges }) => {\n  return (\n    <>\n      {badges.map(({ content, variant, color, classname, title, naked }, i) => (\n        <UserBadge\n          title={title}\n          variant={variant}\n          color={color}\n          className={classname}\n          key={i}\n          naked={naked || false}\n        >\n          {content}\n        </UserBadge>\n      ))}\n    </>\n  );\n};\n\nconst regex = /(^\\w+:|^)\\/\\//;\n\nexport const Website: React.FC<WebsiteProps> = ({ website }) => {\n  return (\n    <a\n      className=\"text-accent mt-3 font-bold\"\n      href={website}\n      target=\"_blank\"\n      rel=\"noreferrer\"\n    >\n      {website.replace(regex, \"\")}\n    </a>\n  );\n};\n\nexport const UserSummaryCard: React.FC<UserSummaryCardProps> = ({\n  onClick,\n  displayName,\n  username,\n  badges,\n  numFollowers,\n  numFollowing,\n  bio,\n  website,\n  isOnline,\n  avatarUrl,\n}) => {\n  const { t } = useTypeSafeTranslation();\n  return (\n    <div className=\"flex flex-col rounded-8 bg-primary-800 p-4 w-full\">\n      <button\n        data-testid=\"edit-profile-widget\"\n        className=\"flex\"\n        onClick={onClick}\n      >\n        <div className=\"flex\">\n          <SingleUser size=\"default\" isOnline={isOnline} src={avatarUrl} />\n        </div>\n        <div className=\"flex mt-2\">\n          <div className=\"flex flex-col ml-3\">\n            <span className=\"text-primary-100 font-bold overflow-hidden break-all text-left\">\n              {displayName}\n            </span>\n            <span className=\"text-primary-300 text-left break-all\">\n              @{username}\n            </span>\n            <span className=\"flex mt-1\">\n              <Badges badges={badges} />\n            </span>\n          </div>\n        </div>\n      </button>\n      <div className=\"flex mt-3\">\n        <div className=\"flex transition duration-200 ease-in-out hover:bg-primary-700 px-2 py-1 rounded-8\">\n          <ApiPreloadLink route=\"followers\" data={{ username }}>\n            <span className=\"text-primary-100 font-bold\">\n              {kFormatter(numFollowers)}\n            </span>\n            <span className=\"text-primary-300 ml-1.5 lowercase\">\n              {t(\"pages.viewUser.followers\")}\n            </span>\n          </ApiPreloadLink>\n        </div>\n        <div className=\"flex transition duration-200 ease-in-out hover:bg-primary-700 px-2 py-1 rounded-8\">\n          <ApiPreloadLink route=\"following\" data={{ username }}>\n            <span className=\"text-primary-100 font-bold\">\n              {kFormatter(numFollowing)}\n            </span>\n            <span className=\"text-primary-300 ml-1.5 lowercase\">\n              {t(\"pages.viewUser.following\")}\n            </span>\n          </ApiPreloadLink>\n        </div>\n      </div>\n      <div\n        data-testid=\"current-user:bio\"\n        className=\"flex text-primary-300 mt-3 break-words text-left\"\n      >\n        {bio}\n      </div>\n      {website && <Website website={website} />}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/UserWideButton.tsx",
    "content": "import React from \"react\";\nimport { SingleUser } from \"./UserAvatar\";\nimport { Button } from \"./Button\";\nimport { BaseUser } from \"@dogehouse/kebab\";\n\nexport interface UserWideButtonInfoProps {\n  user: BaseUser;\n  unblock: () => void;\n}\n\nexport const UserWideButton: React.FC<UserWideButtonInfoProps> = ({\n  user,\n  unblock,\n}) => {\n  return (\n    <>\n      <div className=\"flex items-center justify-between px-4 w-full\">\n        <div className=\"flex\">\n          <SingleUser size=\"sm\" src={user.avatarUrl} username={user.username} />\n          <div className=\"flex flex-col ml-2\">\n            <span className=\"font-bold text-primary-100 whitespace-nowrap\">\n              {user.displayName}\n            </span>\n            <span className=\"text-primary-300 text-sm whitespace-nowrap\">\n              @{user.username}\n            </span>\n          </div>\n        </div>\n        <Button size=\"small\" onClick={unblock}>\n          Unblock\n        </Button>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/VerticalUserInfo.tsx",
    "content": "import { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport normalizeUrl from \"normalize-url\";\nimport React from \"react\";\nimport { linkRegex } from \"../lib/constants\";\nimport { kFormatter } from \"../lib/kFormatter\";\nimport { ApiPreloadLink } from \"../shared-components/ApiPreloadLink\";\nimport { useTypeSafeTranslation } from \"../shared-hooks/useTypeSafeTranslation\";\nimport { SingleUser } from \"./UserAvatar\";\nimport { HeaderController } from \"../modules/display/HeaderController\";\nimport { UserBadge } from \"./UserBadge\";\nimport { badge, Badges } from \"./UserSummaryCard\";\nimport { ContributorBadge, StaffBadge } from \"../icons/badges\";\n\ninterface VerticalUserInfoProps {\n  user: UserWithFollowInfo;\n}\n\nexport const VerticalUserInfo: React.FC<VerticalUserInfoProps> = ({ user }) => {\n  const { t } = useTypeSafeTranslation();\n  const badges: badge[] = [];\n  if (user.staff) {\n    badges.push({\n      content: <StaffBadge />,\n      variant: \"primary\",\n      color: \"white\",\n      title: t(\"components.userBadges.dhStaff\"),\n      naked: true,\n    });\n  }\n  if (user.contributions > 0) {\n    badges.push({\n      content: <ContributorBadge contributions={user.contributions} />,\n      variant: \"primary\",\n      color: \"white\",\n      title: `${t(\"components.userBadges.dhContributor\")} (${user.contributions} ${t(\"pages.admin.contributions\")})`,\n      naked: true,\n    });\n  }\n\n  if (user.botOwnerId) {\n    badges.push({\n      content: t(\"pages.viewUser.bot\"),\n      variant: \"primary\",\n      color: \"white\",\n      title: t(\"pages.viewUser.bot\"),\n    });\n  }\n\n  if (user.followsYou) {\n    badges.push({\n      content: t(\"pages.viewUser.followsYou\"),\n      variant: \"primary-700\",\n      color: \"grey\",\n      title: t(\"pages.viewUser.followsYou\"),\n      classname: \"ml-1\",\n    });\n  }\n  return (\n    <>\n      <HeaderController\n        embed={{}}\n        title={`${user.displayName} (@${user.username})`}\n      />\n      <div className=\"flex flex-col rounded-8 pt-5 px-6 pb-4 w-full items-center\">\n        <ApiPreloadLink route=\"profile\" data={{ username: user.username }}>\n          <SingleUser\n            size=\"default\"\n            src={user.avatarUrl}\n            username={user.username}\n            hover={true}\n          />\n        </ApiPreloadLink>\n        <ApiPreloadLink route=\"profile\" data={{ username: user.username }}>\n          <div className=\"flex mt-2 max-w-full\">\n            <span className=\"flex text-primary-100 font-bold h-full break-all line-clamp-1 truncate\">\n              {user.displayName}\n            </span>\n            <span\n              data-testid=\"profile-info-username\"\n              className=\"flex text-primary-300 ml-1 hover:underline\"\n            >\n              @{user.username}\n            </span>\n          </div>\n        </ApiPreloadLink>\n        <span className=\"flex justify-center mt-2\">\n          <Badges badges={badges} />\n        </span>\n\n        <div className=\"flex mt-2\">\n          <div className=\"flex\">\n            <ApiPreloadLink\n              route=\"followers\"\n              data={{ username: user.username }}\n            >\n              <span className=\"text-primary-100 font-bold\">\n                {kFormatter(user.numFollowers)}\n              </span>\n              <span className=\"text-primary-300 lowercase ml-1.5\">\n                {t(\"pages.viewUser.followers\")}\n              </span>\n            </ApiPreloadLink>\n          </div>\n          <div className=\"flex ml-4\">\n            <ApiPreloadLink\n              route=\"following\"\n              data={{ username: user.username }}\n            >\n              <span className=\"text-primary-100 font-bold\">\n                {kFormatter(user.numFollowing)}\n              </span>\n              <span className=\"text-primary-300 lowercase ml-1.5\">\n                {t(\"pages.viewUser.following\")}\n              </span>\n            </ApiPreloadLink>\n          </div>\n        </div>\n        <div className=\"flex w-full mt-2\">\n          {/* Tailwind's max-height is not working, so I used style */}\n          <p\n            className=\"text-primary-300 mt-2 text-center w-full whitespace-pre-wrap break-words inline overflow-y-auto\"\n            style={{ maxHeight: \"300px\" }}\n          >\n            {user.bio &&\n              user.bio.split(/\\n/).map((line, i) => (\n                <React.Fragment key={i}>\n                  {i > 0 ? <br key={i} /> : null}\n                  {line.split(\" \").map((chunk, j) => {\n                    try {\n                      return linkRegex.test(chunk) ? (\n                        <a\n                          href={normalizeUrl(chunk)}\n                          rel=\"noreferrer\"\n                          className=\"text-accent text-center hover:underline inline\"\n                          key={`${i}${j}`}\n                          target=\"_blank\"\n                        >\n                          {chunk}&nbsp;\n                        </a>\n                      ) : (\n                        <React.Fragment\n                          key={`${i}${j}`}\n                        >{`${chunk} `}</React.Fragment>\n                      );\n                    } catch (err) {}\n\n                    return null;\n                  })}\n                </React.Fragment>\n              ))}\n          </p>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/VolumeIndicator.tsx",
    "content": "import React, { useState } from \"react\";\n\nexport interface VolumeIndicatorProps\n  extends React.HtmlHTMLAttributes<HTMLDivElement> {\n  volume: number /** 0-100 */;\n  bars?: number /** 1-inf */;\n}\n\nexport const VolumeIndicator: React.FC<VolumeIndicatorProps> = ({\n  volume = 0,\n  bars = 24,\n  ...props\n}) => {\n  const hBars: React.ReactNode[] = [];\n  for (let i = 0; i < bars; i++) {\n    hBars.push(\n      <div\n        key={i}\n        className={`h-full rounded-full w-1 transition ${\n          Math.round((volume * bars) / 100) >= i && volume > 0\n            ? \"bg-primary-100\"\n            : \"bg-primary-700\"\n        }`}\n      ></div>\n    );\n  }\n  return (\n    <div {...props} className=\"w-auto h-4 flex gap-1.5\">\n      {hBars}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/VolumeSlider.tsx",
    "content": "import React from \"react\";\nimport Slider from \"rc-slider\";\nimport \"rc-slider/assets/index.css\";\n\nexport interface VolumeSliderProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  label?: boolean;\n  max?: number;\n  volume: number;\n  onVolume: (n: number) => void;\n}\n\nexport const VolumeSlider: React.FC<VolumeSliderProps> = ({\n  label,\n  max = 100,\n  volume,\n  onVolume,\n}) => {\n  return (\n    <div className={`flex flex-col w-full`}>\n      <div\n        className={`flex text-primary-300 w-full text-center justify-center mb-1`}\n      >\n        {label ? \"volume: \" : \"\"} {volume}\n      </div>\n      <Slider\n        min={0}\n        max={max}\n        value={volume}\n        railStyle={{ backgroundColor: \"var(--color-primary-300)\" }}\n        trackStyle={{ backgroundColor: \"var(--color-primary-300)\" }}\n        handleStyle={{\n          backgroundColor: \"var(--color-primary-100)\",\n          border: \"none\",\n        }}\n        onChange={(v) => {\n          onVolume(v);\n        }}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/WinButton.tsx",
    "content": "import React, {\n  ButtonHTMLAttributes,\n  DetailedHTMLProps,\n  ReactNode,\n} from \"react\";\n\nexport type ButtonProps = DetailedHTMLProps<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  HTMLButtonElement\n> & {\n  icon?: ReactNode;\n};\n\nexport const WinButton: React.FC<ButtonProps> = ({\n  children,\n  icon,\n  className = \"\",\n  ...props\n}) => {\n  return (\n    <button\n      className={`flex px-2 py-1 text-xs transition\n      duration-200 ease-in-out text-button\n      bg-primary-700 hover:bg-primary-600 disabled:text-primary-300\n       font-bold items-center justify-center focus:outline-none ${className}`}\n      data-testid=\"button\"\n      {...props}\n    >\n      <span className={`flex items-center`}>\n        {icon ? <span className={`items-center`}>{icon}</span> : null}\n        {children}\n      </span>\n    </button>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/header/LeftHeader.tsx",
    "content": "import Link from \"next/link\";\nimport React from \"react\";\nimport { LgLogo, LogoIcon } from \"../../icons\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\n\nexport interface LeftHeaderProps {}\n\nconst LeftHeader: React.FC<LeftHeaderProps> = ({}) => {\n  const screenType = useScreenType();\n  return (\n    <Link href=\"/dash\">\n      <a data-testid=\"logo-link\" className=\"w-full\">\n        {screenType === \"3-cols\" ? (\n          <LgLogo />\n        ) : (\n          <div className=\"flex justify-center w-full\">\n            <LogoIcon width={40} height={40} color=\"#EFE7DC\" />\n          </div>\n        )}\n      </a>\n    </Link>\n  );\n};\n\nexport default LeftHeader;\n"
  },
  {
    "path": "kibbeh/src/ui/header/MiddleHeader.tsx",
    "content": "import React from \"react\";\nimport { SearchBarController } from \"../../modules/search/SearchBarController\";\nimport { useScreenType } from \"../../shared-hooks/useScreenType\";\nimport LeftHeader from \"./LeftHeader\";\nimport RightHeader from \"./RightHeader\";\n\nexport interface MiddleHeaderProps {}\n\nexport const MiddleHeader: React.FC<MiddleHeaderProps> = () => {\n  const screenType = useScreenType();\n  return (\n    <div className=\"flex flex-1 justify-center w-full\">\n      {screenType === \"fullscreen\" ? (\n        <div className=\"flex mr-4\">\n          <LeftHeader />\n        </div>\n      ) : null}\n      <SearchBarController />\n      {screenType === \"1-cols\" || screenType === \"fullscreen\" ? (\n        <div className=\"flex ml-4\">\n          <RightHeader />\n        </div>\n      ) : null}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/header/RightHeader.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport { useRouter } from \"next/router\";\nimport React from \"react\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { SolidMegaphone, SolidMessages, SolidNotification } from \"../../icons\";\nimport { useTokenStore } from \"../../modules/auth/useTokenStore\";\nimport { closeVoiceConnections } from \"../../modules/webrtc/WebRtcApp\";\nimport { modalConfirm } from \"../../shared-components/ConfirmModal\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { DropdownController } from \"../DropdownController\";\nimport { SettingsDropdown } from \"../SettingsDropdown\";\nimport { SingleUser } from \"../UserAvatar\";\n\nexport interface RightHeaderProps {\n  onAnnouncementsClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => null;\n  onMessagesClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => null;\n  onNotificationsClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => null;\n  actionButton?: React.ReactNode;\n}\n\nconst RightHeader: React.FC<RightHeaderProps> = ({\n  actionButton,\n  onAnnouncementsClick,\n  onMessagesClick,\n  onNotificationsClick,\n}) => {\n  const conn = useConn();\n  const { push } = useRouter();\n  const { t } = useTypeSafeTranslation();\n\n  if (!conn) {\n    return <div />;\n  }\n\n  return (\n    <div className=\"flex space-x-4 items-center justify-end focus:outline-no-chrome w-full\">\n      {onAnnouncementsClick && (\n        <button onClick={onAnnouncementsClick}>\n          <SolidMegaphone width={23} height={23} className=\"text-primary-200\" />\n        </button>\n      )}\n      {onMessagesClick && (\n        <button onClick={onMessagesClick}>\n          <SolidMessages width={23} height={23} className=\"text-primary-200\" />\n        </button>\n      )}\n      {onNotificationsClick && (\n        <button onClick={onNotificationsClick}>\n          <SolidNotification\n            width={23}\n            height={23}\n            className=\"text-primary-200\"\n          />\n        </button>\n      )}\n      {actionButton}\n      <DropdownController\n        zIndex={20}\n        className=\"top-9 right-3 md:right-0 fixed\"\n        innerClassName=\"fixed  transform -translate-x-full\"\n        overlay={(close) => (\n          <SettingsDropdown\n            onActionButtonClicked={() => {\n              modalConfirm(\n                t(\"components.settingsDropdown.logOut.modalSubtitle\"),\n                () => {\n                  conn.close();\n                  closeVoiceConnections(null);\n                  useCurrentRoomIdStore.getState().setCurrentRoomId(null);\n                  useTokenStore\n                    .getState()\n                    .setTokens({ accessToken: \"\", refreshToken: \"\" });\n                  push(\"/logout\");\n                }\n              );\n            }}\n            onCloseDropdown={close}\n            user={conn.user}\n          />\n        )}\n      >\n        <SingleUser\n          className={\"focus:outline-no-chrome\"}\n          size=\"sm\"\n          src={conn.user.avatarUrl}\n        />\n      </DropdownController>\n    </div>\n  );\n};\n\nexport default RightHeader;\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/AccountOverlay.tsx",
    "content": "import React, { useCallback, useEffect, useState } from \"react\";\nimport { useSpring, a, config } from \"react-spring\";\nimport { useDrag } from \"react-use-gesture\";\nimport { useAccountOverlay } from \"../../global-stores/useAccountOverlay\";\nimport { createPortal } from \"react-dom\";\nimport { SettingsIcon } from \"../SettingsIcon\";\nimport {\n  SolidBug,\n  SolidUser,\n  SolidVolume,\n  SolidDiscord,\n  OutlineGlobe,\n  SolidLogOut,\n} from \"../../icons\";\nimport { ApiPreloadLink } from \"../../shared-components/ApiPreloadLink\";\nimport { useTypeSafeTranslation } from \"../../shared-hooks/useTypeSafeTranslation\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { useDebugAudioStore } from \"../../global-stores/useDebugAudio\";\nimport { closeVoiceConnections } from \"../../modules/webrtc/WebRtcApp\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useTokenStore } from \"../../modules/auth/useTokenStore\";\nimport router from \"next/router\";\n\nexport interface AccountOverlyProps {}\n\nconst height = 500 + 40;\n\nexport const AccountOverlay: React.FC<AccountOverlyProps> = ({}) => {\n  const { isOpen, set: setOpen } = useAccountOverlay((s) => s);\n  const { t } = useTypeSafeTranslation();\n  const conn = useConn();\n  const { debugAudio, setDebugAudio } = useDebugAudioStore();\n  const [{ y }, set] = useSpring(() => ({ y: height }));\n\n  const open = useCallback(() => {\n    set({\n      y: 0,\n      immediate: false,\n      config: { mass: 1, tension: 200, friction: 25 },\n    });\n  }, [set]);\n\n  const close = (velocity = 0) => {\n    set({ y: height, immediate: false, config: { ...config.stiff, velocity } });\n    setOpen({ isOpen: false });\n  };\n\n  const bind = useDrag(\n    ({ last, vxvy: [, vy], movement: [, my], cancel, canceled }) => {\n      if (my < -70) cancel();\n\n      if (last) {\n        if (my > height * 0.5 || vy > 0.5) close(vy);\n        else open();\n      } else {\n        set({ y: my, immediate: true });\n      }\n    },\n    {\n      initial: () => [0, y.get()],\n      filterTaps: true,\n      bounds: { top: 0 },\n      rubberband: true,\n    }\n  );\n\n  const display = y.to((py) => (py < height ? \"block\" : \"none\"));\n\n  const bgStyle = {\n    opacity: y.to([0, height], [0.8, 0], \"clamp\"),\n  };\n\n  useEffect(() => {\n    if (isOpen) {\n      open();\n    }\n  }, [isOpen, open]);\n\n  return createPortal(\n    <a.div className=\"absolute w-screen h-full\" style={{ display }}>\n      <a.div\n        className=\"w-screen h-screen absolute left-0 bg-black z-10 opacity-100\"\n        onClick={() => close()}\n        style={bgStyle}\n      ></a.div>\n      <a.div\n        className=\"bg-primary-800 w-full h-full rounded-t-20 relative pt-5\"\n        {...bind()}\n        style={{\n          bottom: `calc(-100% + ${height - 100}px)`,\n          y,\n          zIndex: 11,\n          touchAction: \"none\",\n        }}\n      >\n        <div className=\"bg-primary-600 rounded-full w-6 h-1 absolute top-3 left-2/4 transform -translate-x-1/2\"></div>\n        <div>\n          <ApiPreloadLink\n            data-testid=\"profile-link\"\n            route=\"profile\"\n            data={{ username: conn ? conn.user.username : \"\" }}\n          >\n            <SettingsIcon\n              icon={\n                <SolidUser\n                  className=\"text-primary-100\"\n                  width=\"20\"\n                  height=\"20\"\n                />\n              }\n              label={t(\"components.settingsDropdown.profile\")}\n              onClick={() => close()}\n            />\n          </ApiPreloadLink>\n          <SettingsIcon\n            icon={\n              <OutlineGlobe\n                className=\"text-primary-100\"\n                width=\"20\"\n                height=\"20\"\n              />\n            }\n            label={t(\"components.settingsDropdown.language\")}\n            transition\n            onClick={() => {\n              close();\n              router.push(\"/language\");\n            }}\n          />\n          <a\n            href=\"https://github.com/benawad/dogehouse/issues\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n          >\n            <SettingsIcon\n              icon={\n                <SolidBug className=\"text-primary-100\" width=\"20\" height=\"20\" />\n              }\n              label={t(\"components.settingsDropdown.reportABug\")}\n              onClick={() => close()}\n            />\n          </a>\n          <SettingsIcon\n            label={\n              !debugAudio\n                ? t(\"components.settingsDropdown.debugAudio.debugAudio\")\n                : t(\"components.settingsDropdown.debugAudio.stopDebugger\")\n            }\n            icon={\n              <SolidVolume\n                className=\"text-primary-100\"\n                width=\"20\"\n                height=\"20\"\n              />\n            }\n            onClick={() => setDebugAudio(!debugAudio)}\n          />\n          <a\n            href=\"https://discord.gg/wCbKBZF9cV\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n          >\n            <SettingsIcon\n              icon={\n                <SolidDiscord\n                  className=\"text-primary-100\"\n                  width=\"20\"\n                  height=\"20\"\n                />\n              }\n              label={\"Discord\"}\n              onClick={() => close()}\n            />\n          </a>\n          <SettingsIcon\n            label={t(\"components.settingsDropdown.logOut.button\")}\n            last\n            icon={\n              <SolidLogOut\n                width=\"20\"\n                height=\"20\"\n                className=\"text-primary-100\"\n              />\n            }\n            onClick={() => {\n              conn.close();\n              closeVoiceConnections(null);\n              useCurrentRoomIdStore.getState().setCurrentRoomId(null);\n              useTokenStore\n                .getState()\n                .setTokens({ accessToken: \"\", refreshToken: \"\" });\n              router.push(\"/logout\");\n            }}\n          />\n        </div>\n      </a.div>\n    </a.div>,\n    document.querySelector(\"#__next\")!\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/FeaturedRoomCardAvatars.tsx",
    "content": "import React from \"react\";\nimport { MultipleUsers, SingleUser } from \"../UserAvatar\";\n\nexport type FeaturedRoomCardAvatarsProps = {\n  avatars: string[];\n};\n\nexport const FeaturedRoomCardAvatars: React.FC<FeaturedRoomCardAvatarsProps> = ({\n  avatars,\n}) => {\n  return (\n    <div className=\"flex z-0\">\n      {avatars.slice(0, 2).map((s, i) => (\n        <span\n          key={s + i}\n          className=\"rounded-full bg-primary-800 border-primary-800\"\n          style={{\n            zIndex: avatars.length - i,\n            marginLeft: i > 0 ? -10 : 0,\n            borderWidth: 2,\n            height: 50,\n            width: 50,\n            overflow: \"hidden\",\n          }}\n        >\n          <SingleUser src={s} size=\"md\" />\n        </span>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/FeaturedRoomCardHosts.tsx",
    "content": "import React from \"react\";\nimport { FeaturedRoomCardAvatars } from \"./FeaturedRoomCardAvatars\";\n\nexport type FeaturedRoomCardHostsProps = {\n  avatars: string[];\n  names: string[];\n};\n\nexport const FeaturedRoomCardHosts: React.FC<FeaturedRoomCardHostsProps> = ({\n  avatars,\n  names,\n}) => {\n  return (\n    <div className=\"flex flex-row align-middle\">\n      <FeaturedRoomCardAvatars avatars={avatars} />\n      <div className=\"flex flex-col pl-4 justify-center\">\n        <p className=\"text-primary-300\">Hosted by</p>\n        <p className=\"text-primary-100\">{names.join(\", \")}</p>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/MobileHeader/PageHeader.tsx",
    "content": "import React from \"react\";\nimport { SolidCaretRight } from \"../../../icons\";\n\nexport interface PageHeaderProps {\n  title: string | React.ReactNode;\n  onBackClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => void;\n}\n\nexport const PageHeader: React.FC<PageHeaderProps> = ({\n  title,\n  onBackClick,\n}) => {\n  return (\n    <div className=\"flex w-full px-3 bg-primary-900 text-primary-100 h-8 items-center\">\n      {onBackClick && (\n        <button className=\"absolute\" onClick={onBackClick}>\n          <SolidCaretRight\n            className=\"transform -rotate-180\"\n            height={20}\n            width={20}\n          />\n        </button>\n      )}\n      {title && (\n        <span className=\"mx-auto font-bold text-xl\">\n          {title}\n        </span>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/MobileHeader/ProfileHeader.tsx",
    "content": "import router from \"next/router\";\nimport React, { HTMLProps } from \"react\";\nimport { useAccountOverlay } from \"../../../global-stores/useAccountOverlay\";\nimport { useCurrentRoomIdStore } from \"../../../global-stores/useCurrentRoomIdStore\";\nimport { SolidMessages, SolidNotification, SolidSearch } from \"../../../icons\";\nimport { useTokenStore } from \"../../../modules/auth/useTokenStore\";\nimport { closeVoiceConnections } from \"../../../modules/webrtc/WebRtcApp\";\nimport { modalConfirm } from \"../../../shared-components/ConfirmModal\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { DropdownController } from \"../../DropdownController\";\nimport { SettingsDropdown } from \"../../SettingsDropdown\";\nimport { SingleUser } from \"../../UserAvatar\";\nimport { useTypeSafeTranslation } from \"../../../shared-hooks/useTypeSafeTranslation\";\n\nexport interface ProfileHeaderProps extends HTMLProps<HTMLDivElement> {\n  avatar: string;\n  onAnnouncementsClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => void;\n  onMessagesClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => void;\n  onSearchClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => void;\n}\n\nexport const ProfileHeader: React.FC<ProfileHeaderProps> = ({\n  avatar,\n  onAnnouncementsClick,\n  onMessagesClick,\n  onSearchClick,\n  className = \"\",\n  ...props\n}) => {\n  const { set, isOpen } = useAccountOverlay.getState();\n  const handleClick = () => {\n    if (!isOpen) {\n      set({\n        isOpen: !isOpen,\n      });\n    }\n  };\n\n  return (\n    <div\n      className={`flex w-full p-3 h-8 justify-between items-center bg-primary-900 ${className}`}\n      {...props}\n    >\n      <button onClick={handleClick}>\n        <SingleUser size=\"xxs\" src={avatar} isOnline={true} />\n      </button>\n      <div className=\"flex gap-x-5\">\n        {onAnnouncementsClick && (\n          <button onClick={onAnnouncementsClick}>\n            <SolidNotification\n              className=\"text-primary-100\"\n              height={20}\n              width={20}\n            />\n          </button>\n        )}\n        {onMessagesClick && (\n          <button onClick={onMessagesClick}>\n            <SolidMessages\n              className=\"text-primary-100\"\n              height={20}\n              width={20}\n            />\n          </button>\n        )}\n        {onSearchClick && (\n          <button onClick={onSearchClick}>\n            <SolidSearch className=\"text-primary-100\" height={20} width={20} />\n          </button>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/MobileHeader/SearchHeader.tsx",
    "content": "import React from \"react\";\nimport { SolidCaretRight } from \"../../../icons\";\nimport { SearchBar } from \"../../Search/SearchBar\";\n\nexport interface SearchHeaderProps {\n  onBackClick?: (\n    event: React.MouseEvent<HTMLButtonElement, MouseEvent>\n  ) => void;\n  onSearchChange: (event: React.ChangeEvent<HTMLInputElement>) => void;\n  searchPlaceholder: string;\n  searchLoading: boolean;\n}\n\nexport const SearchHeader: React.FC<SearchHeaderProps> = ({\n  onBackClick,\n  onSearchChange,\n  searchPlaceholder,\n  searchLoading,\n}) => {\n  return (\n    <div\n      className=\"flex w-full px-4 gap-1 bg-primary-900 text-primary-100\"\n      style={{ paddingTop: 17, paddingBottom: 17 }}\n    >\n      {onBackClick && (\n        <button onClick={onBackClick}>\n          <SolidCaretRight\n            className=\"m-auto transform -rotate-180\"\n            height={20}\n            width={20}\n          />\n        </button>\n      )}\n      <SearchBar\n        mobile={true}\n        placeholder={searchPlaceholder}\n        onChange={onSearchChange}\n        isLoading={searchLoading}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/MobileHeader/index.tsx",
    "content": "export { ProfileHeader } from \"./ProfileHeader\";\nexport { PageHeader } from \"./PageHeader\";\nexport { SearchHeader } from \"./SearchHeader\";\n"
  },
  {
    "path": "kibbeh/src/ui/mobile/MobileNav.tsx",
    "content": "import * as React from \"react\";\nimport { useRouter } from \"next/router\";\nimport Link from \"next/link\";\n\nexport interface MobileNavContainerProps {\n  className?: string;\n}\n\nexport const MobileNavContainer: React.FC<MobileNavContainerProps> = ({\n  className,\n  children,\n}) => {\n  return (\n    <div\n      className={`flex fixed inset-x-0 justify-around items-center bottom-0 w-full h-7 bg-primary-900 border-t border-primary-700 ${className}`}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface MobileNavItemProps {\n  targetPath: string;\n}\n\nexport const MobileNavItem: React.FC<MobileNavItemProps> = ({\n  children,\n  targetPath,\n}) => {\n  const router = useRouter();\n  const isActive = router ? router.asPath.includes(targetPath) : false;\n\n  return (\n    <Link href={targetPath}>\n      <div className=\"flex cursor-pointer\">\n        {children &&\n          React.Children.map(children, (child) => {\n            return React.cloneElement(child as React.ReactElement, {\n              className: isActive\n                ? \"text-accent h-4 w-4\"\n                : \"text-primary-100 h-4 w-4\",\n            });\n          })}\n      </div>\n    </Link>\n  );\n};\n\nexport interface NavItem {\n  targetPath: string;\n  icon: JSX.Element;\n}\n\nexport interface MobileNavProps {\n  items: NavItem[];\n}\n\nexport const MobileNav: React.FC<MobileNavProps> = ({ items }) => {\n  const { asPath } = useRouter();\n\n  if (asPath.startsWith(\"/room\")) {\n    return null;\n  }\n\n  return (\n    <MobileNavContainer>\n      {items.map((item) => {\n        return (\n          <MobileNavItem key={item.targetPath} targetPath={item.targetPath}>\n            {item.icon}\n          </MobileNavItem>\n        );\n      })}\n    </MobileNavContainer>\n  );\n};\n"
  },
  {
    "path": "kibbeh/src/ui/tests/BaseOverlay.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { BaseOverlay } from \"../BaseOverlay\";\n\ndescribe(\"BaseOverlay\", () => {\n  it(\"should render correctly\", () => {\n    const { getByTestId, getByText } = render(\n      <BaseOverlay>\n        <div className=\"flex\">Child</div>\n      </BaseOverlay>\n    );\n    const baseOverlay = getByTestId(\"base-overlay\");\n    const child = getByText(\"Child\");\n\n    expect(baseOverlay).toBeVisible();\n    expect(child).toBeVisible();\n  });\n\n  it(\"should render title if title prop is passed\", () => {\n    const { getByText } = render(\n      <BaseOverlay title=\"Messages\">\n        <div className=\"flex\">Child</div>\n      </BaseOverlay>\n    );\n    const title = getByText(\"Messages\");\n\n    expect(title).toBeVisible();\n  });\n\n  it(\"should render actionButton if actionButton prop is passed\", () => {\n    const { getByText } = render(\n      <BaseOverlay actionButton=\"Show more\">\n        <div className=\"flex\">Child</div>\n      </BaseOverlay>\n    );\n    const actionButton = getByText(\"Show more\");\n\n    expect(actionButton).toBeVisible();\n  });\n\n  it(\"should render call onActionButtonClicked if actionButton is clicked\", () => {\n    const onActionButtonClickedMock = jest.fn();\n    const { getByText } = render(\n      <BaseOverlay\n        actionButton=\"Show more\"\n        onActionButtonClicked={onActionButtonClickedMock}\n      >\n        <div className=\"flex\">Child</div>\n      </BaseOverlay>\n    );\n    const actionButton = getByText(\"Show more\");\n\n    actionButton.click();\n\n    expect(onActionButtonClickedMock).toHaveBeenCalled();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(\n      <BaseOverlay>\n        <div className=\"flex\">Child</div>\n      </BaseOverlay>\n    );\n    const baseOverlay = getByTestId(\"base-overlay\");\n\n    expect(baseOverlay).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/BoxedIcon.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { BoxedIcon } from \"../BoxedIcon\";\n\ndescribe(\"BoxedIcon\", () => {\n  describe(\"BoxedIcon\", () => {\n    it(\"should render correctly\", () => {\n      const { getByTestId, getByText } = render(<BoxedIcon>Child</BoxedIcon>);\n      const component = getByTestId(\"boxed-icon\");\n      const child = getByText(\"Child\");\n\n      expect(component).toBeVisible();\n      expect(child).toBeVisible();\n    });\n\n    it(\"should match snapshot\", () => {\n      const { getByTestId } = render(<BoxedIcon />);\n      const component = getByTestId(\"boxed-icon\");\n\n      expect(component).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/BubbleText.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { BubbleText } from \"../BubbleText\";\n\ndescribe(\"BubbleText\", () => {\n  it(\"should render correctly\", () => {\n    const { getByTestId, getByText } = render(<BubbleText>Child</BubbleText>);\n    const component = getByTestId(\"bubble-text\");\n    const child = getByText(\"Child\");\n\n    expect(component).toBeVisible();\n    expect(child).toBeVisible();\n  });\n\n  it(\"should have bg-accent class if live prop is true\", () => {\n    const { getByTestId } = render(<BubbleText live={true}>Child</BubbleText>);\n    const component = getByTestId(\"bubble-text\");\n\n    expect(\n      (component.firstChild as HTMLDListElement).classList.contains(\"bg-accent\")\n    ).toBeTruthy();\n    expect(\n      (component.firstChild as HTMLDListElement).classList.contains(\n        \"bg-primary-300\"\n      )\n    ).toBeFalsy();\n  });\n\n  it(\"should have bg-primary-300 class if live prop is false\", () => {\n    const { getByTestId } = render(<BubbleText live={false}>Child</BubbleText>);\n    const component = getByTestId(\"bubble-text\");\n\n    expect(\n      (component.firstChild as HTMLDListElement).classList.contains(\"bg-accent\")\n    ).toBeFalsy();\n    expect(\n      (component.firstChild as HTMLDListElement).classList.contains(\n        \"bg-primary-300\"\n      )\n    ).toBeTruthy();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<BubbleText>Child</BubbleText>);\n    const component = getByTestId(\"bubble-text\");\n\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/Button.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { Button } from \"../Button\";\n\ndescribe(\"Button\", () => {\n  it(\"should render a button with CLick me text\", () => {\n    const { getByText } = render(<Button>Click me</Button>);\n    const text = getByText(\"Click me\");\n\n    expect(text).toBeVisible();\n  });\n\n  it(\"should render a disabled button if loading prop is passed\", () => {\n    const { getByText } = render(<Button loading={true}>Click me</Button>);\n    const text = getByText(\"Click me\");\n\n    expect(text.closest(\"button\")).toBeDisabled();\n  });\n\n  it(\"should render a disabled button if disabled prop is passed\", () => {\n    const { getByTestId } = render(<Button disabled={true}>Click me</Button>);\n    const button = getByTestId(\"button\");\n\n    expect(button).toBeDisabled();\n  });\n\n  it(\"should render an icon if icon props is passed\", () => {\n    const { getByText, container } = render(\n      <Button icon={<div className=\"flex\">Fake icon</div>}>Click me</Button>\n    );\n    const icon = getByText(\"Fake icon\");\n\n    expect(icon).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<Button disabled={true}>Click me</Button>);\n    const button = getByTestId(\"button\");\n\n    expect(button).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/ErrorMessageButton.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { ErrorMessageButton } from \"../ErrorMessageButton\";\n\ndescribe(\"ErrorMessageButton\", () => {\n  it(\"should render and input correctly\", () => {\n    const { getByTestId } = render(<ErrorMessageButton />);\n    const component = getByTestId(\"error-msg-btn\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<ErrorMessageButton />);\n    const component = getByTestId(\"error-msg-btn\");\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/ErrorToast.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render, act } from \"../../../test-utils\";\nimport { ErrorToast } from \"../Toast\";\n\njest.useFakeTimers();\n\ndescribe(\"ErrorMessage\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"should render correctly\", () => {\n    const message = \"some error message\";\n    const { getByTestId, getByText } = render(\n      <ErrorToast message={message} onClose={() => {}} />\n    );\n    const component = getByTestId(\"error-message\");\n    const child = getByText(message);\n\n    expect(component).toBeVisible();\n    expect(child).toBeVisible();\n  });\n\n  it(\"should call onClose after 7000ms\", () => {\n    const message = \"some error message\";\n    const onClick = jest.fn();\n    render(<ErrorToast message={message} onClose={onClick} />);\n    act(() => {\n      jest.runAllTimers();\n    });\n\n    expect(onClick).toHaveBeenCalled();\n  });\n\n  it(\"shouldn't call onClose after 7000ms if duration props is sticky\", () => {\n    const message = \"some error message\";\n    const onClick = jest.fn();\n    render(\n      <ErrorToast message={message} onClose={onClick} duration=\"sticky\" />\n    );\n    act(() => {\n      jest.runAllTimers();\n    });\n\n    expect(onClick).not.toHaveBeenCalled();\n  });\n\n  it(\"should call onClose the close button is clicked\", () => {\n    const message = \"some error message\";\n    const onClick = jest.fn();\n\n    const { getByTestId } = render(\n      <ErrorToast message={message} onClose={onClick} />\n    );\n\n    const closeBtn = getByTestId(\"close-btn\");\n    closeBtn.click();\n    expect(onClick).toHaveBeenCalled();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(\n      <ErrorToast message=\"\" onClose={() => {}} />\n    );\n    const component = getByTestId(\"error-message\");\n\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/Input.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { Input, InputProps } from \"../Input\";\n\n// jest.useFakeTimers();\n\n// const friendOnline: FriendOnlineType = {\n//   avatar: \"\",\n//   isOnline: true,\n//   username: \"Some name\",\n//   activeRoom: {\n//     name: \"Some room\",\n//     link: \"\",\n//   },\n// };\n\n// const showMoreAction = jest.fn();\n\n// let defaultProps: InputProps = {\n//   onlineFriendCount: 1,\n//   onlineFriendList: [friendOnline],\n//   showMoreAction,\n// };\n\ndescribe(\"Input\", () => {\n  it(\"should render and input correctly\", () => {\n    const { getByTestId } = render(<Input />);\n    const component = getByTestId(\"input\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should render and input correctly\", () => {\n    const { getByTestId } = render(<Input textarea={true} />);\n    const component = getByTestId(\"textarea\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<Input />);\n    const component = getByTestId(\"input\");\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/InputErrorMsg.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { InputErrorMsg } from \"../InputErrorMsg\";\n\ndescribe(\"InputErrorMsg\", () => {\n  it(\"should render correctly\", () => {\n    const { getByTestId } = render(<InputErrorMsg />);\n    const component = getByTestId(\"input-error-msg\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<InputErrorMsg />);\n    const component = getByTestId(\"input-error-msg\");\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/MainGrid.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { MainGrid } from \"../MainGrid\";\n\ndescribe(\"MainGrid\", () => {\n  it(\"should render correctly\", () => {\n    const { getByTestId, getByText } = render(<MainGrid>Child</MainGrid>);\n    const component = getByTestId(\"main-grid\");\n    const child = getByText(\"Child\");\n\n    expect(component).toBeVisible();\n    expect(child).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<MainGrid>Child</MainGrid>);\n    const component = getByTestId(\"main-grid\");\n\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/MessageElement.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { MessageElement } from \"../MessageElement\";\n\ndescribe(\"MessageElement\", () => {\n  it(\"should render correctly\", () => {\n    const { getByTestId } = render(\n      <MessageElement\n        msg={{ text: \"some text\", ts: 123 }}\n        user={{ avatar: \"\", isOnline: true, username: \"someUserNAme\" }}\n      />\n    );\n    const component = getByTestId(\"msg-element\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(\n      <MessageElement\n        msg={{ text: \"some text\", ts: 123 }}\n        user={{ avatar: \"\", isOnline: true, username: \"someUserNAme\" }}\n      />\n    );\n    const component = getByTestId(\"msg-element\");\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/MessagesDropdown.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport { MessageElementProps } from \"../MessageElement\";\nimport { MessagesDropdown } from \"../MessagesDropdown\";\n\nconst messageList: MessageElementProps[] = [\n  {\n    msg: {\n      text: \"message text 1\",\n      ts: 1234,\n    },\n    user: {\n      username: \"username\",\n      avatar: \"\",\n      isOnline: true,\n    },\n  },\n  {\n    msg: {\n      text: \"message text 2\",\n      ts: 12344,\n    },\n    user: {\n      username: \"username1\",\n      avatar: \"\",\n      isOnline: true,\n    },\n  },\n];\n\ndescribe(\"MessagesDropdown\", () => {\n  it(\"should render 3 message elements\", () => {\n    const { getAllByTestId } = render(\n      <MessagesDropdown messageList={messageList} />\n    );\n    const component = getAllByTestId(\"msg-element\");\n\n    expect(component.length).toBe(2);\n  });\n\n  it(\"should render empty state message elements if messageList length is 0\", () => {\n    const { getByTestId } = render(<MessagesDropdown messageList={[]} />);\n\n    const component = getByTestId(\"empty-state-msg\");\n\n    expect(component).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/MinimizedRoomCard.spec.tsx",
    "content": "import React from \"react\";\n\nimport { render } from \"../../../test-utils\";\nimport {\n  MinimizedRoomCard,\n  MinimizedRoomCardProps,\n} from \"../MinimizedRoomCard\";\n\nconst defaultProps: MinimizedRoomCardProps = {\n  room: {\n    name: \"\",\n    roomStartedAt: new Date(),\n    speakers: [\"user1\", \"user2\", \"user3\"],\n    myself: {\n      isDeafened: false,\n      isMuted: false,\n      isSpeaker: true,\n      switchDeafened: () => {},\n      leave: () => {},\n      switchMuted: () => {},\n    },\n  },\n};\n\ndescribe(\"MinimizedRoomCard\", () => {\n  it(\"should render correctly\", () => {\n    const { getByTestId } = render(<MinimizedRoomCard {...defaultProps} />);\n    const component = getByTestId(\"minimized-room-card\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should render mic icon if myself isn't muted\", () => {\n    const { getByTestId } = render(<MinimizedRoomCard {...defaultProps} />);\n    const component = getByTestId(\"mic-on\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should render mic-off icon if myself is muted\", () => {\n    const props: MinimizedRoomCardProps = {\n      ...defaultProps,\n      room: {\n        ...defaultProps.room,\n        myself: {\n          ...defaultProps.room.myself,\n          isMuted: true,\n        },\n      },\n    };\n    const { getByTestId } = render(<MinimizedRoomCard {...props} />);\n    const component = getByTestId(\"mic-off\");\n\n    expect(component).toBeVisible();\n  });\n\n  it(\"should match snapshot\", () => {\n    const { getByTestId } = render(<MinimizedRoomCard {...defaultProps} />);\n    const component = getByTestId(\"minimized-room-card\");\n\n    expect(component).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/BaseOverlay.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`BaseOverlay should match snapshot 1`] = `\n<div\n  class=\"flex flex-col w-full rounded-8 bg-primary-800 border border-primary-700 overflow-hidden relative\"\n  data-testid=\"base-overlay\"\n>\n  \n  <div\n    class=\"flex flex-col text-primary-100\"\n  >\n    <div\n      class=\"flex\"\n    >\n      Child\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/BoxedIcon.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`BoxedIcon BoxedIcon should match snapshot 1`] = `\n<button\n  class=\"flex bg-primary-700  hover:bg-primary-600 h-6 w-6 cursor-pointer justify-center items-center rounded-8 text-primary-100\n        \"\n  data-testid=\"boxed-icon\"\n/>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/BubbleText.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`BubbleText should match snapshot 1`] = `\n<div\n  class=\"text-primary-200 font-bold items-center\"\n  data-testid=\"bubble-text\"\n>\n  <div\n    class=\"inline-block mr-2 w-2 h-2 rounded-full bg-primary-300\"\n  />\n  Child\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/Button.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Button should match snapshot 1`] = `\n<button\n  class=\"flex outline-none focus:ring-4 focus:ring-primary py-2 px-6 text-sm rounded-lg  text-button bg-accent transition duration-200 ease-in-out hover:bg-accent-hover disabled:text-accent-disabled disabled:bg-accent-hover font-bold flex items-center justify-center \"\n  data-testid=\"button\"\n  disabled=\"\"\n>\n  <span\n    class=\"flex items-center\"\n  >\n    Click me\n  </span>\n</button>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/ErrorMessageButton.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ErrorMessageButton should match snapshot 1`] = `\n<button\n  class=\"rounded-lg px-3 font-bold text-sm bg-secondary-washed-out undefined\"\n  data-testid=\"error-msg-btn\"\n  style=\"padding-top: 3px; padding-bottom: 3px;\"\n/>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/ErrorToast.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ErrorMessage should match snapshot 1`] = `\n<div\n  class=\"flex rounded-8 p-3 relative w-full items-center justify-center text-button transition-transform duration-300 bg-secondary\"\n  data-testid=\"error-message\"\n>\n  <div\n    class=\"flex absolute cursor-pointer\"\n    data-testid=\"close-btn\"\n    style=\"top: 5px; right: 7px; width: 13px; height: 13px;\"\n  >\n    <svg\n      fill=\"currentColor\"\n      height=\"16\"\n      style=\"transform: rotate(45deg);\"\n      viewBox=\"0 0 16 16\"\n      width=\"16\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <g\n        clip-path=\"url(#clip12)\"\n      >\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M8 0C8.55228 0 9 0.447715 9 1V15C9 15.5523 8.55228 16 8 16C7.44772 16 7 15.5523 7 15V1C7 0.447715 7.44772 0 8 0Z\"\n          fill=\"#DEE3EA\"\n          fill-rule=\"evenodd\"\n        />\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M0.000976562 8C0.000976562 7.44772 0.448692 7 1.00098 7H15.001C15.5533 7 16.001 7.44772 16.001 8C16.001 8.55228 15.5533 9 15.001 9H1.00098C0.448692 9 0.000976562 8.55228 0.000976562 8Z\"\n          fill=\"#DEE3EA\"\n          fill-rule=\"evenodd\"\n        />\n      </g>\n      <defs>\n        <clippath\n          id=\"clip12\"\n        >\n          <rect\n            fill=\"white\"\n            height=\"16\"\n            width=\"16\"\n          />\n        </clippath>\n      </defs>\n    </svg>\n  </div>\n  <div\n    class=\"flex space-x-4 items-center\"\n  >\n    <div\n      class=\"bold\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/Input.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Input should match snapshot 1`] = `\n<input\n  class=\"w-full py-2 px-4 rounded-8 text-primary-100 placeholder-primary-300 focus:outline-none bg-primary-700  undefined \"\n  data-testid=\"input\"\n/>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/InputErrorMsg.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`InputErrorMsg should match snapshot 1`] = `\n<div\n  class=\"flex text-secondary\"\n  data-testid=\"input-error-msg\"\n/>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/MainGrid.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`MainGrid should match snapshot 1`] = `\n<div\n  class=\"flex justify-center w-full min-h-screen bg-primary-900\"\n  data-testid=\"main-grid\"\n>\n  <div\n    class=\"relative w-full px-3 \"\n    id=\"main\"\n    style=\"display: flex; grid-template-columns: 1fr; column-gap: 60px;\"\n  >\n    Child\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/MessageElement.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`MessageElement should match snapshot 1`] = `\n<div\n  class=\"items-center w-full px-4 md:bg-primary-800 md:border-b md:border-primary-600 cursor-pointer hover:bg-primary-700 bg-primary-900\"\n  data-testid=\"msg-element\"\n>\n  <div\n    class=\"flex mr-3\"\n  >\n    <div\n      class=\"relative inline-block \"\n      data-testid=\"single-user-avatar\"\n      style=\"width: 40px; height: 40px;\"\n    >\n      <img\n        alt=\"your-avatar\"\n        class=\"rounded-full w-full h-full object-cover \"\n        src=\"\"\n      />\n      <span\n        class=\"rounded-full absolute box-content bg-accent border-primary-800\"\n        data-testid=\"online-indictor\"\n        style=\"width: 8px; height: 8px; right: 2px; bottom: -2px; border-width: 2px;\"\n      />\n    </div>\n  </div>\n  <div\n    class=\"flex-col py-3 border-b border-primary-600 md:border-none md:py-0\"\n    style=\"width: calc(100% - 50px);\"\n  >\n    <div\n      class=\"flex justify-between\"\n    >\n      <span\n        class=\"text-button font-bold inline-block truncate mr-1\"\n        style=\"line-height: 22px;\"\n      >\n        someUserNAme\n      </span>\n      <span\n        class=\"text-primary-300 text-sm font-medium inline-block truncate\"\n        style=\"line-height: 22px;\"\n      >\n        over 51 years\n         ago\n      </span>\n    </div>\n    <div\n      class=\"block text-sm text-primary-300 font-medium truncate w-9/12\"\n      style=\"line-height: 22px;\"\n    >\n      some text\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/src/ui/tests/__snapshots__/MinimizedRoomCard.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`MinimizedRoomCard should match snapshot 1`] = `\n<div\n  class=\"bg-primary-800 border border-accent rounded-lg p-4 gap-4 grid max-w-md w-full\"\n  data-testid=\"minimized-room-card\"\n>\n  <div\n    class=\"gap-1 grid\"\n  >\n    <h4\n      class=\"text-primary-100 break-all overflow-hidden\"\n    />\n    <div\n      class=\"text-primary-300 overflow-ellipsis overflow-hidden w-auto\"\n    >\n      user1, user2, user3\n    </div>\n    <div\n      class=\"text-accent\"\n    >\n      components.bottomVoiceControl.speaker\n       \n      · \n      00:00\n    </div>\n  </div>\n  <div\n    class=\"flex flex-row\"\n  >\n    <div\n      class=\"grid grid-cols-3 gap-2\"\n    >\n      <button\n        class=\"flex bg-primary-700 transition duration-200 ease-in-out hover:bg-primary-600 h-6 w-6 cursor-pointer justify-center items-center rounded-8 \n        bg-accent hover:bg-accent-hover text-button\"\n        data-testid=\"mute\"\n      >\n        <svg\n          data-testid=\"mic-on\"\n          fill=\"currentColor\"\n          height=\"16\"\n          viewBox=\"0 0 16 16\"\n          width=\"16\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M8 11.205a3.2 3.2 0 003.196-3.197V3.213C11.196 1.442 9.77 0 8.016 0a.746.746 0 00-.167.02 3.201 3.201 0 00-3.046 3.193v4.795a3.2 3.2 0 003.196 3.197z\"\n          />\n          <path\n            d=\"M7.2 14.346V16H8.8v-1.654c3.148-.395 5.594-3.083 5.594-6.338h-1.598A4.8 4.8 0 018 12.803a4.8 4.8 0 01-4.795-4.795H1.606c0 3.255 2.447 5.943 5.595 6.338z\"\n          />\n        </svg>\n      </button>\n      <button\n        class=\"flex bg-primary-700  hover:bg-primary-600 h-6 w-6 cursor-pointer justify-center items-center rounded-8 text-primary-100\n        \"\n        data-testid=\"deafen\"\n      >\n        <svg\n          data-testid=\"headphone-on\"\n          fill=\"currentColor\"\n          height=\"16\"\n          viewBox=\"0 0 16 16\"\n          width=\"16\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M13.5414 2.33786C12.0154 0.852143 9.99572 0 8 0C6.00429 0 3.98464 0.852143 2.45857 2.33786C0.873214 3.88107 0 5.89286 0 8C0 8.9525 0.3125 10.1818 1.17429 12.4839C2.03607 14.7861 3.75 15.75 4.47393 15.9075C4.68143 15.9529 4.92464 16 5.14286 16C5.51887 16.0003 5.88836 15.9018 6.21429 15.7143L6.71429 15.4286C7.2525 15.1136 7.40964 14.4239 7.1 13.8804L3.99571 8.43143C3.92174 8.30104 3.82264 8.18663 3.70414 8.09481C3.58564 8.003 3.4501 7.93561 3.30537 7.89654C3.16064 7.85748 3.0096 7.84752 2.86099 7.86724C2.71239 7.88696 2.56917 7.93597 2.43964 8.01143L1.95071 8.29714C1.7628 8.40723 1.59395 8.54701 1.45071 8.71107C1.43294 8.73193 1.40953 8.74724 1.38329 8.75516C1.35706 8.76309 1.32909 8.7633 1.30274 8.75577C1.27638 8.74824 1.25275 8.73329 1.23466 8.7127C1.21657 8.69211 1.20479 8.66675 1.20071 8.63964C1.16472 8.42827 1.14537 8.2144 1.14286 8C1.14286 6.20321 1.89286 4.48286 3.25571 3.15679C4.57143 1.87679 6.29964 1.14286 8 1.14286C9.70036 1.14286 11.4286 1.87679 12.7443 3.15679C14.1071 4.48286 14.8571 6.20321 14.8571 8C14.8539 8.21446 14.8339 8.42833 14.7971 8.63964C14.7931 8.66675 14.7813 8.69211 14.7632 8.7127C14.7451 8.73329 14.7215 8.74824 14.6951 8.75577C14.6688 8.7633 14.6408 8.76309 14.6146 8.75516C14.5883 8.74724 14.5649 8.73193 14.5471 8.71107C14.4039 8.54701 14.2351 8.40723 14.0471 8.29714L13.5582 8.01143C13.4287 7.93597 13.2855 7.88696 13.1369 7.86724C12.9883 7.84752 12.8372 7.85748 12.6925 7.89654C12.5478 7.93561 12.4122 8.003 12.2937 8.09481C12.1752 8.18663 12.0761 8.30104 12.0021 8.43143L8.9 13.8804C8.59036 14.4239 8.7475 15.1136 9.28572 15.4286L9.78572 15.7143C10.1116 15.9018 10.4811 16.0003 10.8571 16C11.0754 16 11.3186 15.9529 11.5261 15.9075C12.25 15.75 13.9643 14.7857 14.8257 12.4839C15.6871 10.1821 16 8.9525 16 8C16 5.89286 15.1268 3.88107 13.5414 2.33786Z\"\n          />\n        </svg>\n      </button>\n      <button\n        class=\"flex bg-primary-700 transition duration-200 ease-in-out hover:bg-primary-600 h-6 w-6 cursor-pointer justify-center items-center rounded-8 text-primary-100\n        \"\n        data-testid=\"boxed-icon\"\n      >\n        <svg\n          fill=\"currentColor\"\n          height=\"16\"\n          viewBox=\"0 0 16 16\"\n          width=\"16\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            clip-rule=\"evenodd\"\n            d=\"M2.797 1.559h.934a.78.78 0 000-1.559H.747A.764.764 0 000 .78v3.118a.747.747 0 101.492 0v-1.5l3.144 3.328a.755.755 0 101.066-1.069L2.797 1.56zm10.38.01h-.945a.785.785 0 010-1.569h3.014a.768.768 0 01.753.785v3.143a.755.755 0 11-1.507 0v-1.51l-3.177 3.353a.762.762 0 11-1.078-1.079l2.94-3.123zM2.796 14.441h.934a.78.78 0 010 1.559H.747A.764.764 0 010 15.22v-3.118a.747.747 0 111.492 0v1.5l3.144-3.328a.755.755 0 111.066 1.069L2.797 14.44zm10.38-.01h-.945a.785.785 0 000 1.569h3.014a.767.767 0 00.753-.784v-3.143a.755.755 0 10-1.507 0v1.508l-3.177-3.352a.762.762 0 10-1.078 1.079l2.94 3.123z\"\n            fill-rule=\"evenodd\"\n          />\n        </svg>\n      </button>\n    </div>\n    <button\n      class=\"flex outline-none focus:ring-4 focus:ring-primary-300 py-2 px-6 text-sm rounded-lg transition duration-200 ease-in-out text-button bg-primary-700 hover:bg-primary-600 disabled:text-primary-300 font-bold flex items-center justify-center flex-grow ml-4\"\n      data-testid=\"button\"\n    >\n      <span\n        class=\"flex items-center\"\n      >\n        components.bottomVoiceControl.leave\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "kibbeh/tailwind.config.js",
    "content": "/* eslint-disable global-require */\nmodule.exports = {\n  darkMode: \"class\",\n  purge: {\n    content: [\"./src/**/*.tsx\", \"./public/index.html\"],\n    options: {\n      safelist: [\"h-8\", \"h-11\"],\n    },\n  },\n  theme: {\n    fontFamily: {\n      sans: [\n        \"Inter\",\n        \"-apple-system\",\n        \"BlinkMacSystemFont\",\n        \"Segoe UI\",\n        \"Roboto\",\n        \"Helvetica\",\n        \"Arial\",\n        \"sans-serif\",\n      ],\n      mono: [\"Menlo\", \"Monaco\", \"Courier New\", \"monospace\"],\n    },\n    fontSize: {\n      tiny: \"0.625rem\",\n      xs: \".75rem\",\n      sm: \".875rem\",\n      base: \"1rem\",\n      lg: \"1.125rem\",\n      xl: \"1.25rem\",\n      \"2xl\": \"1.5rem\",\n      \"3xl\": \"1.875rem\",\n      \"4xl\": \"2.25rem\",\n      \"5xl\": \"3rem\",\n      \"6xl\": \"4rem\",\n      \"7xl\": \"5rem\",\n    },\n    colors: {\n      button: \"var(--color-button-text)\",\n      transparent: \"transparent\",\n      primary: {\n        100: \"var(--color-primary-100)\",\n        200: \"var(--color-primary-200)\",\n        300: \"var(--color-primary-300)\",\n        600: \"var(--color-primary-600)\",\n        700: \"var(--color-primary-700)\",\n        800: \"var(--color-primary-800)\",\n        900: \"var(--color-primary-900)\",\n      },\n      secondary: {\n        DEFAULT: \"var(--color-secondary)\",\n        \"washed-out\": \"var(--color-secondary-washed-out)\",\n      },\n      accent: {\n        DEFAULT: \"var(--color-accent)\",\n        hover: \"var(--color-accent-hover)\",\n        disabled: \"var(--color-accent-disabled)\",\n      },\n      black: \"#000\",\n    },\n    spacing: {\n      0: \"0px\",\n      1: \"5px\",\n      1.5: \"6px\",\n      2: \"10px\",\n      3: \"15px\",\n      4: \"20px\",\n      4.5: \"25px\",\n      5: \"30px\",\n      5.5: \"35px\",\n      6: \"40px\",\n      6.5: \"50px\",\n      7: \"60px\",\n      7.5: \"65px\",\n      8: \"75px\",\n      9: \"80px\",\n      10: \"90px\",\n      11: \"100px\",\n      15: \"150px\",\n      \"5l\": \"10rem\",\n      \"n1/2\": \"-50%\",\n      24: \"24rem\",\n      400: \"400px\",\n    },\n\n    boxShadow: {\n      outlineLg: \"0 0 0 4pt var(--color-primary-800)\",\n      outlineMd: \"0 0 0 2pt var(--color-primary-800)\",\n      outlineSm: \"0 0 0 1pt var(--color-primary-800)\",\n    },\n    borderWidth: {\n      DEFAULT: \"1px\",\n      0: \"0px\",\n      4: \"4px\",\n      2: \"2px\",\n    },\n    extend: {\n      borderRadius: {\n        5: \"5px\",\n        8: \"8px\",\n        20: \"20px\",\n        40: \"40px\",\n      },\n      borderColor: {\n        \"color-800\": \"var(--color-primary-800)\",\n      },\n      outline: {\n        \"no-chrome\": \"none\",\n      },\n      transitionTimingFunction: {\n        \"in-out-hard\": \"cubic-bezier(.77, 0, .175, 1)\",\n      },\n      transitionDuration: {\n        400: \"400ms\",\n      },\n      keyframes: {\n        breathe: {\n          \"0%, 100%\": {\n            boxShadow: \"0 0 20px 2px var(--color-primary-100-translucent)\",\n            borderColor: \"var(--color-primary-300)\",\n          },\n          \"50%\": {\n            boxShadow: \"0 0 20px 2px transparent\",\n            borderColor: \"var(--color-primary-700)\",\n          },\n        },\n      },\n      animation: {\n        \"breathe-slow\": \"breathe 5s infinite ease-in-out\",\n      },\n    },\n  },\n  variants: {\n    backgroundColor: ({ after }) => after([\"disabled\"]),\n    textColor: ({ after }) => after([\"disabled\"]),\n    scrollbar: [\"rounded\", \"dark\"],\n    extend: {\n      borderWidth: [\"last\"],\n    },\n  },\n  plugins: [require(\"tailwind-scrollbar\"), require(\"@tailwindcss/line-clamp\")],\n};\n"
  },
  {
    "path": "kibbeh/test-utils.ts",
    "content": "import { render } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport \"mutationobserver-shim\";\n\nconst customRender = (ui: any, options = {}) =>\n  render(ui, {\n    ...options,\n  });\n\nexport * from \"@testing-library/react\";\n\nexport { customRender as render };\n"
  },
  {
    "path": "kibbeh/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"sourceMap\": true,\n    \"target\": \"es6\",\n    \"jsx\": \"preserve\",\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true,\n    \"noEmitOnError\": false,\n    \"resolveJsonModule\": true,\n    \"importHelpers\": true,\n    \"strict\": true,\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"types\": [\"@types/jest\"],\n    \"module\": \"commonjs\",\n    \"esModuleInterop\": true,\n    \"preserveSymlinks\": true,\n    \"typeRoots\": [\"./node_modules/@types\"],\n    \"downlevelIteration\": true,\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"isolatedModules\": true\n    // \"baseUrl\": \"src\"\n  },\n  \"include\": [\"next-env.d.ts\", \"src/**/*.ts\", \"src/**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "kousa/.envrc",
    "content": "export GITHUB_CLIENT_ID=nyahnyahnyah\nexport GITHUB_CLIENT_SECRET=foobarbaz\n\nexport BEN_GITHUB_ID=nopenope\nexport ACCESS_TOKEN_SECRET=oneuthateuhathoeuthuaonehntoauhaeuheae\nexport REFRESH_TOKEN_SECRET=zzaneuthaonueheaouhnaoehunstasenuthanu\n\nexport TWITTER_API_KEY=ntauhnaehuntaeeuntehnatu\nexport TWITTER_SECRET_KEY=taeounaheunaoehutaheueteee\nexport TWITTER_BEARER_TOKEN=naoeuhetuhnau\n\nexport DISCORD_CLIENT_ID=ntahunhaoneuhtohauehonu\nexport DISCORD_CLIENT_SECRET=aoneunthaehouh\n\nexport GOOGLE_CLIENT_ID=ntahunhaoneuhtohauehonu\nexport GOOGLE_CLIENT_SECRET=aoneunthaehouh\n\nexport TWITTER_BEARER_TOKEN=aoidjwqdjqiowdjwqadjoqwidoqw\n\nexport DATABASE_URL=postgres://asdouaoisdwionasa"
  },
  {
    "path": "kousa/.formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "kousa/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nkousa-*.tar\n\n\n# Temporary files for e.g. tests\n/tmp\n\n.elixir_ls\n_build\n.env\nlog"
  },
  {
    "path": "kousa/ADDING_TO_MESSAGING_API.md",
    "content": "# ADDING NEW ROUTES TO THE WS API\n\n1.  Add the name of the route to the manifest (Broth.Message.Manifest)\n2.  Add the module type and some number to the enum (Broth.Message.Types.Operator)\n\n3.  create the module in the appropriate Broth.Message.(\\*) directory.\n    - `use Broth.Message.Cast` if it's a cast, or `use Broth.Message.Call`\n      if it's a Call, it will assume a `Reply` submodule.\n    - if you need to put data in place using the websocket state, use\n      `def initialize(state)` -> `%__MODULE__{...}`\n    - you must provide a schema.  This sets up the contract for the\n      contents of the payload message.\n    - you must write a changeset/2 function.  This validates the\n      payload.\n    - you must write an execute/2 function.  This is passed to\n      the changeset from before, and performs the action.\n      -  if it's a cast, it expects `{:noreply, state}`\n      -  if it's a call, it expects `{:reply, %__MODULE__{...}, state}`\n         or `{:error, message}`\n      - note that if you have a reply, the execute function must come\n        *after* the Reply module definition.\n    - if it's a schema that creates or edits a database table, refer to\n      one of the other messages that creates or edits, for now.\n4.  write schema validation tests in `test/broth/_message/*`.  This\n    helps prevent some malicious payloads.\n5.  write end-to-end tests in `test/broth/*`\n\n6. (only v0.1.0) add a inbound translation and an outbound\n   translation route to `Broth.Message.Translator.V0_1_0`\n7. (only v0.1.0) write the appropriate test in either `test/broth/_calls` or `test/broth/_casts`\n"
  },
  {
    "path": "kousa/ARCHITECTURE.md",
    "content": "# Kousa Architecture Document\n\nKousa will roughly follow the HyperHypeBeast hexagonal, or\n\"functional core\" architecture:\n\nhttps://www.youtube.com/watch?v=yTkzNHF6rMs\n\nElixir contexts\n\n1. `Beef` - Database, persistent state for Kousa\n - `Beef.Access` nonmutating queries\n - `Beef.Changesets` ingress validation logic\n - `Beef.Mutations` mutating queries\n - `Beef.Queries` composable Ecto.Query fragments\n - `Beef.Schemas` database table schemas\n - `Beef.Lenses` database struct logic\n2. `Onion` - OTP-based transient state for Kousa\n3. `Broth` - Web interface and contexts\n - `Broth.Messages` - contracts for all ws I/O\n4. `Kousa` - OTP Application, Business Logic, and common toolsets\n\nNB: All of the module contexts will be part of the `:kousa` BEAM VM\napplication under the application supervision tree\n"
  },
  {
    "path": "kousa/Dockerfile",
    "content": "# STEP 1 - BUILD RELEASE \nFROM hexpm/elixir:1.11.3-erlang-23.2.7-alpine-3.13.2 AS build\n\n# Install build dependencies\nRUN apk update && \\\n    apk upgrade --no-cache && \\\n    apk add --no-cache \\\n    git \\\n    build-base \\\n    nodejs-current \\\n    nodejs-npm \\ \n    python3 && \\ \n    mix local.rebar --force && \\\n    mix local.hex --force\n\nENV MIX_ENV=prod\nWORKDIR /app\n\n# Install elixir package dependencies\nCOPY mix.exs /app/mix.exs\nCOPY mix.lock /app/mix.lock\nRUN mix do deps.get --only $MIX_ENV, deps.compile\n\n# copy config, priv and release and application directories\nCOPY config /app/config\nCOPY priv /app/priv\nCOPY lib /app/lib\n\n# compile app and create release\nRUN mix do compile, release\n\n####################################################################################################\n# STEP 2 - FINAL\nFROM alpine:3.13.2 as app\nRUN apk add --no-cache openssl ncurses-libs\n\nWORKDIR /app\n\nRUN chown nobody:nobody /app\n\nUSER nobody:nobody\n\nCOPY --from=build --chown=nobody:nobody /app/_build/prod/rel/kousa ./\n\nENV HOME=/app\n\nCMD bin/kousa eval \"Kousa.Release.migrate\" && bin/kousa start\n"
  },
  {
    "path": "kousa/README.md",
    "content": "# Kousa - Elixir Backend\n\n## Running Tests\n\n`mix test`\n\nCoverage report: `mix coveralls.html`.\nPretty information outputted into `cover/excoveralls.html`\n\n## Running as Development\n\n1. Install dependencies with `mix deps.get`\n2. Create a GitHub, Twitter and Discord OAuth application and paste credentials in `.envrc`\n3. Compile and build kousa `mix do compile, release`\n4. Run kousa `_build/dev/rel/kousa/bin/kousa start`\n\n## Autoformatting\n\n`mix format`\n\n## Style ENFORCEMENT\n\n`mix credo --strict`\n\n## for Elixir NOOBs:\n\nGeneral Elixir conventions:\n\n- A function that ends in `!` can crash on documented error conditions.\n- `is_...` should only be used for guards; most boolean functions should be\n  `...?` functions\n- `fetch...` should return `{:ok, value}` or `:error`\n- `fetch...!` should return `value` or crash if not present\n- `get...` should return `value` or `nil`\n\n`snake_case` not `camelCase`\n"
  },
  {
    "path": "kousa/ROADMAP.md",
    "content": "# Kousa WS API Roadmap\n\n## 0.2.0 (inbound route reform)\n- soft-deprecate 0.1.0 routes\n- implement 0.2.0 routes\n- validation of inbound routes\n\n## 0.3.0 (general outbound route reform)\n- reform the route transformation pipeline (internal only)\n- autogenerate API shape documentation\n- begin tracking api versioning in the socket\n- hard deprecate 0.1.0 inbound routes\n- soft-deprecate 0.1.0/0.2.0 outbound routes\n- implement 0.3.0 outbound routes\n  - soft-deprecate \"initial\" boolean in the reply routes\n\n## Unscheduled (maybe never)\n- API-wide switch from camelCase to snake_case parameters API-wide"
  },
  {
    "path": "kousa/config/config.exs",
    "content": "use Mix.Config\n\nconfig :kousa, ecto_repos: [Beef.Repo]\nconfig :kousa, max_room_size: 1000\nconfig :kousa, websocket_auth_timeout: 10_000\nconfig :kousa, message_character_limit: 512\n\nconfig :extwitter, :json_library, Jason\n\nimport_config \"#{Mix.env()}.exs\"\n"
  },
  {
    "path": "kousa/config/dev.exs",
    "content": "use Mix.Config\n\nconfig :logger, level: :info\n\ndatabase_url =\n  System.get_env(\"DATABASE_URL\") ||\n    \"postgres://postgres:postgres@localhost/kousa_repo2\"\n\nconfig :kousa, Beef.Repo, url: database_url\n\n# TODO: remove system environment variables and make\n# dev deployment easier\n\nconfig :ueberauth, Ueberauth.Strategy.Github.OAuth,\n  client_id:\n    System.get_env(\"GITHUB_CLIENT_ID\") ||\n      raise(\"\"\"\n      environment variable GITHUB_CLIENT_ID is missing.\n      Create an oauth application on GitHub to get one\n      \"\"\"),\n  client_secret:\n    System.get_env(\"GITHUB_CLIENT_SECRET\") ||\n      raise(\"\"\"\n      environment variable GITHUB_CLIENT_SECRET is missing.\n      Create an oauth application on GitHub to get one\n      \"\"\")\n\nconfig :kousa,\n  num_voice_servers: 1,\n  web_url: System.get_env(\"WEB_URL\") || \"http://localhost:3000\",\n  api_url: System.get_env(\"API_URL\") || \"http://localhost:4001\",\n  secret_key_base:\n    \"213lo12j3kl21j3kl21alaksjdklasjdklajsldjsaldjlasdlaksjdklasjdklajsldjsaldjlasdlaksjdklasjdklajsldjsaldjlasdadjlasjddlkijoqwijdoqwjd12loki3jhl12jelk12jekl1221099dj120\",\n  env: :dev,\n  ben_github_id:\n    System.get_env(\"BEN_GITHUB_ID\") ||\n      raise(\"\"\"\n      environment variable BEN_GITHUB_ID is missing.\n      \"\"\"),\n  access_token_secret:\n    System.get_env(\"ACCESS_TOKEN_SECRET\") ||\n      raise(\"\"\"\n      environment variable ACCESS_TOKEN_SECRET is missing.\n      type some random characters to create one\n      \"\"\"),\n  refresh_token_secret:\n    System.get_env(\"REFRESH_TOKEN_SECRET\") ||\n      raise(\"\"\"\n      environment variable REFRESH_TOKEN_SECRET is missing.\n      type some random characters to create one\n      \"\"\")\n\nconfig :joken,\n  access_token_secret: System.fetch_env!(\"ACCESS_TOKEN_SECRET\"),\n  refresh_token_secret: System.fetch_env!(\"REFRESH_TOKEN_SECRET\")\n\nconfig :ueberauth, Ueberauth.Strategy.Google.OAuth,\n  client_id:\n    System.get_env(\"GOOGLE_CLIENT_ID\") ||\n      raise(\"\"\"\n      environment variable GOOGLE_CLIENT_ID is missing.\n      Create an oauth application on Google to get one\n      \"\"\"),\n  client_secret:\n    System.get_env(\"GOOGLE_CLIENT_SECRET\") ||\n      raise(\"\"\"\n        environment variable GOOGLE_CLIENT_SECRET is missing.\n        Create an oauth application on Google to get one\n      \"\"\")\n\nconfig :ueberauth, Ueberauth.Strategy.Twitter.OAuth,\n  consumer_key:\n    System.get_env(\"TWITTER_API_KEY\") ||\n      raise(\"\"\"\n      environment variable TWITTER_API_KEY is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  consumer_secret:\n    System.get_env(\"TWITTER_SECRET_KEY\") ||\n      raise(\"\"\"\n        environment variable TWITTER_SECRET_KEY is missing.\n        Create an oauth application on Twitter to get one\n      \"\"\")\n\nconfig :ueberauth, Ueberauth.Strategy.Discord.OAuth,\n  client_id:\n    System.get_env(\"DISCORD_CLIENT_ID\") ||\n      raise(\"\"\"\n      environment variable DISCORD_CLIENT_ID is missing.\n      Create an oauth application on Discord to get one\n      \"\"\"),\n  client_secret:\n    System.get_env(\"DISCORD_CLIENT_SECRET\") ||\n      raise(\"\"\"\n      environment variable DISCORD_CLIENT_SECRET is missing.\n      Create an oauth application on Discord to get one\n      \"\"\")\n\nconfig :extwitter, :oauth,\n  consumer_key:\n    System.get_env(\"TWITTER_API_KEY\") ||\n      raise(\"\"\"\n      environment variable TWITTER_API_KEY is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  consumer_secret:\n    System.get_env(\"TWITTER_SECRET_KEY\") ||\n      raise(\"\"\"\n      environment variable TWITTER_SECRET_KEY is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  access_token:\n    System.get_env(\"TWITTER_BEARER_TOKEN\") ||\n      raise(\"\"\"\n      environment variable TWITTER_BEARER_TOKEN is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  access_token_secret: \"\"\n"
  },
  {
    "path": "kousa/config/prod.exs",
    "content": "use Mix.Config\n\nconfig :logger, level: :info, backends: [:console, Sentry.LoggerBackend]\nconfig :kousa, env: :prod\n"
  },
  {
    "path": "kousa/config/releases.exs",
    "content": "import Config\n\ndatabase_url =\n  System.get_env(\"DATABASE_URL\") ||\n    raise \"\"\"\n    environment variable DATABASE_URL is missing.\n    For example: ecto://USER:PASS@HOST/DATABASE\n    \"\"\"\n\nconfig :kousa, Beef.Repo, url: database_url\n\nconfig :ueberauth, Ueberauth.Strategy.Github.OAuth,\n  client_id:\n    System.get_env(\"GITHUB_CLIENT_ID\") ||\n      raise(\"\"\"\n      environment variable GITHUB_CLIENT_ID is missing.\n      Create an oauth application on GitHub to get one\n      \"\"\"),\n  client_secret:\n    System.get_env(\"GITHUB_CLIENT_SECRET\") ||\n      raise(\"\"\"\n      environment variable GITHUB_CLIENT_SECRET is missing.\n      Create an oauth application on GitHub to get one\n      \"\"\")\n\nconfig :ueberauth, Ueberauth.Strategy.Twitter.OAuth,\n  consumer_key:\n    System.get_env(\"TWITTER_API_KEY\") ||\n      raise(\"\"\"\n        environment variable TWITTER_API_KEY is missing.\n        Create an oauth application on Twitter to get one\n      \"\"\"),\n  consumer_secret:\n    System.get_env(\"TWITTER_SECRET_KEY\") ||\n      raise(\"\"\"\n      environment variable TWITTER_SECRET_KEY is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\")\n\nconfig :ueberauth, Ueberauth.Strategy.Discord.OAuth,\n  client_id:\n    System.get_env(\"DISCORD_CLIENT_ID\") ||\n      raise(\"\"\"\n      environment variable DISCORD_CLIENT_ID is missing.\n      Create an oauth application on Discord to get one\n      \"\"\"),\n  client_secret:\n    System.get_env(\"DISCORD_CLIENT_SECRET\") ||\n      raise(\"\"\"\n      environment variable DISCORD_CLIENT_SECRET is missing.\n      Create an oauth application on Discord to get one\n      \"\"\")\n\nconfig :ueberauth, Ueberauth.Strategy.Google.OAuth,\n  client_id:\n    System.get_env(\"GOOGLE_CLIENT_ID\") ||\n      raise(\"\"\"\n      environment variable GOOGLE_CLIENT_ID is missing.\n      Create an oauth application on Google to get one\n      \"\"\"),\n  client_secret:\n    System.get_env(\"GOOGLE_CLIENT_SECRET\") ||\n      raise(\"\"\"\n        environment variable GOOGLE_CLIENT_SECRET is missing.\n        Create an oauth application on Google to get one\n      \"\"\")\n\nconfig :kousa,\n  num_voice_servers: 1,\n  staging?: System.get_env(\"IS_STAGING\") == \"true\",\n  secret_key_base:\n    System.get_env(\"SECRET_KEY_BASE\") ||\n      raise(\"\"\"\n      environment variable SECRET_KEY_BASE is missing.\n      \"\"\"),\n  ben_github_id:\n    System.get_env(\"BEN_GITHUB_ID\") ||\n      raise(\"\"\"\n      environment variable BEN_GITHUB_ID is missing.\n      \"\"\"),\n  rabbit_url:\n    System.get_env(\"RABBITMQ_URL\") ||\n      raise(\"\"\"\n      environment variable RABBITMQ_URL is missing.\n      \"\"\"),\n  web_url: System.get_env(\"WEB_URL\") || \"https://dogehouse.tv\",\n  api_url: System.get_env(\"API_URL\") || \"https://api.dogehouse.tv\",\n  access_token_secret:\n    System.get_env(\"ACCESS_TOKEN_SECRET\") ||\n      raise(\"\"\"\n      environment variable ACCESS_TOKEN_SECRET is missing.\n      type some random characters to create one\n      \"\"\"),\n  refresh_token_secret:\n    System.get_env(\"REFRESH_TOKEN_SECRET\") ||\n      raise(\"\"\"\n      environment variable REFRESH_TOKEN_SECRET is missing.\n      type some random characters to create one\n      \"\"\")\n\nif(System.get_env(\"SENTRY_DNS\") != nil) do\n  IO.warn(\"The SENTRY_DNS environment variable is deprecated, use SENTRY_DSN instead\")\nend\n\nconfig :sentry,\n  dsn:\n    System.get_env(\"SENTRY_DSN\") || System.get_env(\"SENTRY_DNS\") ||\n      raise(\"\"\"\n      environment variable SENTRY_DSN is missing.\n      \"\"\"),\n  environment_name: :prod,\n  enable_source_code_context: true,\n  root_source_code_path: File.cwd!(),\n  tags: %{\n    env: \"production\"\n  },\n  included_environments: [:prod]\n\nconfig :joken,\n  access_token_key: System.fetch_env!(\"ACCESS_TOKEN_SECRET\"),\n  refresh_token_key: System.fetch_env!(\"REFRESH_TOKEN_SECRET\")\n\nconfig :extwitter, :oauth,\n  consumer_key:\n    System.get_env(\"TWITTER_API_KEY\") ||\n      raise(\"\"\"\n      environment variable TWITTER_API_KEY is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  consumer_secret:\n    System.get_env(\"TWITTER_SECRET_KEY\") ||\n      raise(\"\"\"\n      environment variable TWITTER_SECRET_KEY is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  access_token:\n    System.get_env(\"TWITTER_BEARER_TOKEN\") ||\n      raise(\"\"\"\n      environment variable TWITTER_BEARER_TOKEN is missing.\n      Create an oauth application on Twitter to get one\n      \"\"\"),\n  access_token_secret: \"\"\n\nIO.puts(\"staging?:\")\nIO.puts(Application.get_env(:kousa, :staging?))\n"
  },
  {
    "path": "kousa/config/test.exs",
    "content": "import Config\n\ndatabase_url = System.get_env(\"DATABASE_URL\") || \"postgres://postgres:postgres@localhost/kousa_repo2_test\"\n\nconfig :kousa, Beef.Repo,\n  url: database_url,\n  pool: Ecto.Adapters.SQL.Sandbox\n\nconfig :logger, level: :error\n\nif System.get_env(\"GITHUB_ACTIONS\") do\n  config :app, App.Repo,\n    username: \"postgres\",\n    password: \"postgres\"\nend\n\nconfig :kousa,\n  web_url: System.get_env(\"WEB_URL\") || \"http://localhost:3000\",\n  api_url: System.get_env(\"API_URL\") || \"http://localhost:4001\",\n  access_token_secret: \"thisistheaccesstokenfortest\",\n  refresh_token_secret: \"thisistherefreshtokenfortest\",\n  ben_github_id: \"notreallybensgithubid\"\n\nconfig :kousa, websocket_auth_timeout: 50\n"
  },
  {
    "path": "kousa/deploy.sh",
    "content": "#!/bin/bash\nset -e\n\ndocker build -t benawad/kousa:0.0.2 .\ndocker save benawad/kousa:0.0.2 | bzip2 | ssh doge-api \"bunzip2 | docker load\"\nssh doge-api \"docker tag benawad/kousa:0.0.2 dokku/api:latest && dokku tags:deploy api latest\""
  },
  {
    "path": "kousa/lib/beef/_repo.ex",
    "content": "defmodule Beef.Repo do\n  use Ecto.Repo,\n    otp_app: :kousa,\n    adapter: Ecto.Adapters.Postgres\nend\n"
  },
  {
    "path": "kousa/lib/beef/access/rooms.ex",
    "content": "defmodule Beef.Access.Rooms do\n  import Ecto.Query\n  @fetch_limit 16\n\n  alias Beef.Queries.Rooms, as: Query\n  alias Beef.Users\n  alias Beef.UserBlocks\n  alias Beef.Repo\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n  alias Beef.Schemas.UserBlock\n  alias Beef.Schemas.RoomBlock\n  alias Beef.RoomPermissions\n  alias Beef.RoomBlocks\n\n  def get_room_status(user_id) do\n    room = Users.get_current_room(user_id)\n\n    cond do\n      is_nil(room) ->\n        {nil, nil}\n\n      room.creatorId == user_id ->\n        {:creator, room}\n\n      true ->\n        {case RoomPermissions.get(user_id, room.id) do\n           %{isMod: true} -> :mod\n           %{isSpeaker: true} -> :speaker\n           %{askedToSpeak: true} -> :askedToSpeak\n           _ -> :listener\n         end, room}\n    end\n  end\n\n  def can_join_room(room_id, user_id) do\n    room = get_room_by_id(room_id)\n    max_room_size = Application.fetch_env!(:kousa, :max_room_size)\n\n    case room do\n      nil ->\n        {:error, \"room doesn't exist anymore\"}\n\n      _ ->\n        cond do\n          room.numPeopleInside >= max_room_size ->\n            {:error, \"room is full\"}\n\n          RoomBlocks.blocked?(room_id, user_id) ->\n            {:error, \"you are blocked from the room\"}\n\n          true ->\n            if UserBlocks.blocked?(room.creatorId, user_id) do\n              {:error, \"the creator of the room blocked you\"}\n            else\n              {:ok, room}\n            end\n        end\n    end\n  end\n\n  def get_top_public_rooms(user_id, offset \\\\ 0) do\n    max_room_size = Application.fetch_env!(:kousa, :max_room_size)\n\n    items =\n      from(r in Room,\n        left_join: rb in RoomBlock,\n        on: rb.roomId == r.id and rb.userId == ^user_id,\n        left_join: ub in UserBlock,\n        on:\n          (r.creatorId == ub.userIdBlocked and ub.userId == ^user_id) or\n            (r.creatorId == ub.userId and ub.userIdBlocked == ^user_id),\n        where:\n          is_nil(ub.userIdBlocked) and is_nil(rb.roomId) and r.isPrivate == false and\n            r.numPeopleInside < ^max_room_size,\n        order_by: [desc: r.numPeopleInside],\n        offset: ^offset,\n        limit: ^@fetch_limit\n      )\n      |> Repo.all()\n\n    {Enum.slice(items, 0, -1 + @fetch_limit),\n     if(length(items) == @fetch_limit, do: -1 + offset + @fetch_limit, else: nil)}\n  end\n\n  @spec get_room_by_id(any) :: any\n  def get_room_by_id(room_id) do\n    Repo.get(Room, room_id)\n  end\n\n  @user_order \"\"\"\n    (case\n      when ? then 1\n      else 2\n    end)\n  \"\"\"\n  @spec get_next_creator_for_room(any) :: any\n  def get_next_creator_for_room(room_id) do\n    from(u in User,\n      inner_join: rp in Beef.Schemas.RoomPermission,\n      on: rp.roomId == ^room_id and rp.userId == u.id and u.currentRoomId == ^room_id,\n      where: rp.isSpeaker == true,\n      limit: 1,\n      order_by: [\n        asc: fragment(@user_order, rp.isMod)\n      ]\n    )\n    |> Repo.one()\n  end\n\n  def get_a_user_for_room(room_id) do\n    Query.userStart()\n    |> Query.filter_by_current_room_id(room_id)\n    |> Query.limit_one()\n    |> Repo.one()\n  end\n\n  def get_room_by_creator_id(creator_id) do\n    Query.start()\n    |> Query.filter_by_creator_id(creator_id)\n    |> Query.limit_one()\n    |> Repo.one()\n  end\n\n  def owner?(room_id, user_id) do\n    not is_nil(\n      Query.start()\n      |> Query.filter_by_room_id_and_creator_id(room_id, user_id)\n      |> Repo.one()\n    )\n  end\n\n  def search_name(start_of_name) do\n    search_str = start_of_name <> \"%\"\n\n    Query.start()\n    |> where([r], ilike(r.name, ^search_str) and r.isPrivate == false)\n    |> order_by([r], desc: r.numPeopleInside)\n    |> limit([], 15)\n    |> Repo.all()\n  end\n\n  @spec all_rooms :: any\n  def all_rooms() do\n    Repo.all(Room)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/access/user_blocks.ex",
    "content": "defmodule Beef.Access.UserBlocks do\n  @moduledoc \"\"\"\n    DB Access Functions for UserBlocks Table\n  \"\"\"\n\n  # alias Beef.Schemas.UserBlock\n  alias Beef.Repo\n  alias Beef.Queries.UserBlocks, as: Query\n\n  def blocked?(user_id, user_id_blocked) do\n    not is_nil(\n      Query.start()\n      |> Query.filter_by_id_and_blockedId(user_id, user_id_blocked)\n      |> Repo.one()\n    )\n  end\n\n  @spec username_blocked?(any, any) :: boolean\n  def username_blocked?(username, user_id_blocked) do\n    not is_nil(\n      Query.start()\n      |> Query.filter_by_username_and_blockedId(username, user_id_blocked)\n      |> Repo.one()\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/access/users.ex",
    "content": "defmodule Beef.Access.Users do\n  import Ecto.Query, warn: false\n\n  alias Beef.Queries.Users, as: Query\n  alias Beef.Repo\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n  alias Beef.Rooms\n\n  def get(user_id) do\n    Repo.get(User, user_id)\n  end\n\n  def get(user_id, opts) do\n    # opts could be a preload.\n  end\n\n  def find_by_github_ids(ids) do\n    Query.start()\n    |> Query.filter_by_github_ids(ids)\n    |> Query.select_id()\n    |> Repo.all()\n  end\n\n  def search_username(<<first_letter>> <> rest) when first_letter == ?@ do\n    search_username(rest)\n  end\n\n  def search_username(start_of_username) do\n    search_str = start_of_username <> \"%\"\n\n    Query.start()\n    # here\n    |> where([u], ilike(u.username, ^search_str))\n    |> order_by([u], desc: u.numFollowers)\n    |> limit([], 15)\n    |> Repo.all()\n  end\n\n  @spec get_by_id_with_follow_info(any, any) :: any\n  def get_by_id_with_follow_info(me_id, them_id) do\n    Query.start()\n    |> Query.filter_by_id(them_id)\n    |> select([u], u)\n    |> Query.follow_info(me_id)\n    |> Query.i_blocked_them_info(me_id)\n    |> Query.they_blocked_me_info(me_id)\n    |> Query.limit_one()\n    |> Repo.one()\n  end\n\n  def get_by_id(user_id) do\n    Repo.get(User, user_id)\n  end\n\n  def get_by_id_with_room_permissions(user_id) do\n    from(u in User,\n      where: u.id == ^user_id,\n      left_join: rp in Beef.Schemas.RoomPermission,\n      on: rp.userId == u.id and rp.roomId == u.currentRoomId,\n      select: %{u | roomPermissions: rp},\n      limit: 1\n    )\n    |> Repo.one()\n  end\n\n  def get_by_username(username) do\n    Query.start()\n    |> Query.filter_by_username(username)\n    |> Repo.one()\n  end\n\n  def get_by_username_with_follow_info(user_id, username) do\n    Query.start()\n    |> Query.filter_by_username(username)\n    |> select([u], u)\n    |> Query.follow_info(user_id)\n    |> Query.i_blocked_them_info(user_id)\n    |> Query.they_blocked_me_info(user_id)\n    |> Query.limit_one()\n    |> Repo.one()\n  end\n\n  @fetch_limit 16\n  def search(query, offset) do\n    query_with_percent = \"%\" <> query <> \"%\"\n\n    items =\n      from(u in User,\n        where:\n          ilike(u.username, ^query_with_percent) or\n            ilike(u.displayName, ^query_with_percent),\n        left_join: cr in Room,\n        on: u.currentRoomId == cr.id and cr.isPrivate == false,\n        select: %{u | currentRoom: cr},\n        limit: @fetch_limit,\n        offset: ^offset\n      )\n      |> Repo.all()\n\n    {Enum.slice(items, 0, -1 + @fetch_limit),\n     if(length(items) == @fetch_limit, do: -1 + offset + @fetch_limit, else: nil)}\n  end\n\n  def get_users_in_current_room(user_id) do\n    case tuple_get_current_room_id(user_id) do\n      {:ok, nil} ->\n        {nil, []}\n\n      {:ok, current_room_id} ->\n        {current_room_id,\n         from(u in User,\n           where: u.currentRoomId == ^current_room_id,\n           left_join: rp in Beef.Schemas.RoomPermission,\n           on: rp.userId == u.id and rp.roomId == u.currentRoomId,\n           select: %{u | roomPermissions: rp}\n         )\n         |> Repo.all()}\n\n      _ ->\n        {nil, []}\n    end\n  end\n\n  # NB: Anything that touches Gen will have to be refactored away\n  # out of the database layer, but we are keeping it here for now\n  # to keep the transition smooth.\n  def tuple_get_current_room_id(user_id) do\n    # DO NOT COPY/PASTE THIS FUNCTION\n    case Onion.UserSession.get_current_room_id(user_id) do\n      {:ok, nil} ->\n        {nil, nil}\n\n      x ->\n        {:ok, x}\n    end\n  end\n\n  @spec get_by_id_with_current_room(any) :: any\n  def get_by_id_with_current_room(user_id) do\n    from(u in User,\n      left_join: a0 in assoc(u, :currentRoom),\n      where: u.id == ^user_id,\n      limit: 1,\n      preload: [\n        currentRoom: a0\n      ]\n    )\n    |> Repo.one()\n  end\n\n  def get_current_room(user_id) do\n    room_id = get_current_room_id(user_id)\n\n    case room_id do\n      nil -> nil\n      id -> Rooms.get_room_by_id(id)\n    end\n  end\n\n  def get_current_room_id(user_id) do\n    # DO NOT COPY/PASTE THIS FUNCTION\n    try do\n      Onion.UserSession.get_current_room_id(user_id)\n    catch\n      _, _ ->\n        case get_by_id(user_id) do\n          nil -> nil\n          %{currentRoomId: id} -> id\n        end\n    end\n  end\n\n  def get_ip(user_id) do\n    # DO NOT COPY/PASTE THIS FUNCTION\n    try do\n      Onion.UserSession.get(user_id, :ip)\n    catch\n      _, _ ->\n        case get_by_id(user_id) do\n          nil -> nil\n          %{ip: ip} -> ip\n        end\n    end\n  end\n\n  def bot?(user_id) do\n    # DO NOT COPY/PASTE THIS FUNCTION\n    try do\n      not is_nil(Onion.UserSession.get(user_id, :bot_owner_id))\n    catch\n      _, _ ->\n        case get_by_id(user_id) do\n          nil -> nil\n          %{botOwnerId: botOwnerId} -> not is_nil(botOwnerId)\n        end\n    end\n  end\n\n  def get_by_api_key(api_key) do\n    Repo.get_by(User, apiKey: api_key)\n  end\n\n  def count_bot_accounts(user_id) do\n    Repo.one(from(u in User, select: fragment(\"count(*)\"), where: u.botOwnerId == ^user_id))\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/follows.ex",
    "content": "defmodule Beef.Follows do\n  @moduledoc \"\"\"\n  Context module for Follows.\n\n  This will eventually go away and be replaced\n  by using associations on users.\n  \"\"\"\n\n  import Ecto.Query\n\n  @fetch_limit 21\n\n  alias Beef.Schemas.Follow\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n\n  @spec get_followers_online_and_not_in_a_room(String.t()) :: [Follow.t()]\n  def get_followers_online_and_not_in_a_room(user_id) do\n    from(\n      f in Follow,\n      inner_join: u in User,\n      on: f.followerId == u.id,\n      where: f.userId == ^user_id and u.online == true and is_nil(u.currentRoomId)\n    )\n    |> Beef.Repo.all()\n  end\n\n  def bulk_insert(follows) do\n    Beef.Repo.insert_all(\n      Follow,\n      follows,\n      on_conflict: :nothing\n    )\n  end\n\n  # TODO: change the name of this, is_ by convention means\n  # \"guard\".\n  def following_me?(user_id, user_id_to_check) do\n    not is_nil(\n      from(\n        f in Follow,\n        where: f.userId == ^user_id and f.followerId == ^user_id_to_check\n      )\n      |> Beef.Repo.one()\n    )\n  end\n\n  # fetch all the users\n  def get_my_following(user_id, offset \\\\ 0) do\n    items =\n      from(\n        f in Follow,\n        inner_join: u in User,\n        on: f.userId == u.id,\n        left_join: f2 in Follow,\n        on: f2.userId == ^user_id and f2.followerId == u.id,\n        left_join: cr in Room,\n        on: u.currentRoomId == cr.id,\n        where:\n          f.followerId == ^user_id and\n            (is_nil(cr.isPrivate) or\n               cr.isPrivate == false),\n        select: %{\n          u\n          | currentRoom: cr,\n            followsYou: not is_nil(f2.userId),\n            youAreFollowing: true\n        },\n        limit: ^@fetch_limit,\n        offset: ^offset,\n        order_by: [desc: u.online]\n      )\n      |> Beef.Repo.all()\n\n    {Enum.slice(items, 0, -1 + @fetch_limit),\n     if(length(items) == @fetch_limit, do: -1 + offset + @fetch_limit, else: nil)}\n  end\n\n  def fetch_invite_list(user_id, offset \\\\ 0) do\n    user = Beef.Users.get_by_id(user_id)\n\n    items =\n      from(\n        f in Follow,\n        inner_join: u in User,\n        on: f.followerId == u.id,\n        where:\n          f.userId == ^user_id and u.online == true and\n            (u.currentRoomId != ^user.currentRoomId or is_nil(u.currentRoomId)),\n        select: u,\n        limit: ^@fetch_limit,\n        offset: ^offset\n      )\n      |> Beef.Repo.all()\n\n    {Enum.slice(items, 0, -1 + @fetch_limit),\n     if(length(items) == @fetch_limit, do: -1 + offset + @fetch_limit, else: nil)}\n  end\n\n  def get_followers(user_id, user_id_to_get_followers_for, offset \\\\ 20) do\n    items =\n      from(\n        f in Follow,\n        where: f.userId == ^user_id_to_get_followers_for,\n        inner_join: u in User,\n        on: f.followerId == u.id,\n        left_join: f2 in Follow,\n        on: f2.userId == u.id and f2.followerId == ^user_id,\n        select: %{u | youAreFollowing: not is_nil(f2.userId)},\n        limit: ^@fetch_limit,\n        offset: ^offset,\n        order_by: [desc: f.inserted_at]\n      )\n      |> Beef.Repo.all()\n\n    {Enum.slice(items, 0, -1 + @fetch_limit),\n     if(length(items) == @fetch_limit, do: -1 + offset + @fetch_limit, else: nil)}\n  end\n\n  def get_following(user_id, user_id_to_get_following_for, offset \\\\ 20) do\n    items =\n      from(\n        f in Follow,\n        where: f.followerId == ^user_id_to_get_following_for,\n        inner_join: u in User,\n        on: f.userId == u.id,\n        left_join: f2 in Follow,\n        on: f2.userId == u.id and f2.followerId == ^user_id,\n        select: %{u | youAreFollowing: not is_nil(f2.userId)},\n        limit: ^@fetch_limit,\n        offset: ^offset,\n        order_by: [desc: f.inserted_at]\n      )\n      |> Beef.Repo.all()\n\n    {Enum.slice(items, 0, -1 + @fetch_limit),\n     if(length(items) == @fetch_limit, do: -1 + offset + @fetch_limit, else: nil)}\n  end\n\n  def delete(user_id, follower_id) do\n    {rows_affected, _} =\n      from(f in Follow, where: f.userId == ^user_id and f.followerId == ^follower_id)\n      |> Beef.Repo.delete_all()\n\n    if rows_affected == 1 do\n      from(u in User,\n        where: u.id == ^user_id,\n        update: [\n          inc: [\n            numFollowers: -1\n          ]\n        ]\n      )\n      |> Beef.Repo.update_all([])\n\n      from(u in User,\n        where: u.id == ^follower_id,\n        update: [\n          inc: [\n            numFollowing: -1\n          ]\n        ]\n      )\n      |> Beef.Repo.update_all([])\n    end\n  end\n\n  def insert(data) do\n    %Follow{}\n    |> Follow.insert_changeset(data)\n    |> Beef.Repo.insert()\n    |> case do\n      {:ok, _} ->\n        # TODO: eliminate N+1 by setting up changesets\n        # in an idiomatic fashion.\n\n        from(u in User,\n          where: u.id == ^data.userId,\n          update: [\n            inc: [\n              numFollowers: 1\n            ]\n          ]\n        )\n        |> Beef.Repo.update_all([])\n\n        from(u in User,\n          where: u.id == ^data.followerId,\n          update: [\n            inc: [\n              numFollowing: 1\n            ]\n          ]\n        )\n        |> Beef.Repo.update_all([])\n\n      error ->\n        error\n    end\n  end\n\n  def get_follow(me_id, other_user_id) do\n    from(f in Follow,\n      where: f.userId == ^me_id and f.followerId == ^other_user_id,\n      limit: 1\n    )\n    |> Beef.Repo.one()\n  end\n\n  def get_info(me_id, other_user_id) do\n    from(f in Follow,\n      where:\n        (f.userId == ^me_id and f.followerId == ^other_user_id) or\n          (f.userId == ^other_user_id and f.followerId == ^me_id),\n      limit: 2\n    )\n    |> Beef.Repo.all()\n    |> case do\n      # when both follow each other there should be two results.\n      [_, _] ->\n        %{followsYou: true, youAreFollowing: true}\n\n      # when following is unidirectional, there should be one result.\n      # this susses out the direction of that relationship\n      [%{userId: ^me_id, followerId: ^other_user_id}] ->\n        %{followsYou: true, youAreFollowing: false}\n\n      [%{userId: ^other_user_id, followerId: ^me_id}] ->\n        %{followsYou: false, youAreFollowing: true}\n\n      # no relationship, no entries.\n      [] ->\n        %{followsYou: false, youAreFollowing: false}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/mutations/rooms.ex",
    "content": "defmodule Beef.Mutations.Rooms do\n  import Ecto.Query\n\n  alias Beef.Repo\n  alias Beef.Schemas.Room\n  alias Beef.Users\n  alias Beef.Schemas.User\n\n  def set_room_privacy_by_creator_id(user_id, isPrivate, new_name) do\n    from(r in Room,\n      where: r.creatorId == ^user_id,\n      update: [\n        set: [\n          isPrivate: ^isPrivate,\n          name: ^new_name\n        ]\n      ],\n      select: r\n    )\n    |> Repo.update_all([])\n  end\n\n  def join_room(room, user_id) do\n    user = Users.set_current_room(user_id, room.id, room.isPrivate, true)\n\n    if (length(room.peoplePreviewList) < 10 or\n          not is_nil(\n            Enum.find(room.peoplePreviewList, fn x ->\n              x.numFollowers < user.numFollowers\n            end)\n          )) and is_nil(Enum.find(room.peoplePreviewList, &(&1.id === user_id))) do\n      list =\n        [\n          %User.Preview{\n            id: user.id,\n            displayName: user.displayName,\n            numFollowers: user.numFollowers,\n            avatarUrl: user.avatarUrl\n          }\n          | room.peoplePreviewList\n        ]\n        |> Enum.sort(&(&1.numFollowers >= &2.numFollowers))\n        |> Enum.slice(0, 10)\n\n      increment_room_people_count(room.id, list)\n    else\n      increment_room_people_count(room.id)\n    end\n\n    user\n  end\n\n  def increment_room_people_count(room_id) do\n    from(u in Room,\n      where: u.id == ^room_id,\n      update: [\n        inc: [\n          numPeopleInside: 1\n        ]\n      ]\n    )\n    |> Repo.update_all([])\n  end\n\n  def increment_room_people_count(room_id, new_people_list) do\n    from(u in Room,\n      where: u.id == ^room_id,\n      update: [\n        inc: [\n          numPeopleInside: 1\n        ],\n        set: [\n          peoplePreviewList: ^new_people_list\n        ]\n      ]\n    )\n    |> Repo.update_all([])\n  end\n\n  def delete_room_by_id(room_id) do\n    %Room{id: room_id} |> Repo.delete()\n  end\n\n  def decrement_room_people_count(room_id, new_people_list) do\n    from(r in Room,\n      where: r.id == ^room_id,\n      update: [\n        inc: [\n          numPeopleInside: -1\n        ],\n        set: [\n          peoplePreviewList: ^new_people_list\n        ]\n      ]\n    )\n    |> Beef.Repo.update_all([])\n  end\n\n  def set_room_owner_and_dec(room_id, user_id, new_people_list) do\n    from(u in Room,\n      where: u.id == ^room_id,\n      update: [\n        set: [\n          creatorId: ^user_id,\n          peoplePreviewList: ^new_people_list\n        ],\n        inc: [\n          numPeopleInside: -1\n        ]\n      ]\n    )\n    |> Repo.update_all([])\n  end\n\n  def replace_room_owner(user_id, new_creator_id) do\n    from(r in Room,\n      where: r.creatorId == ^user_id,\n      update: [\n        set: [\n          creatorId: ^new_creator_id\n        ]\n      ]\n    )\n    |> Repo.update_all([])\n  end\n\n  # trusts that the user is in the room\n  def kick_from_room(user_id, room_id) do\n    room = Beef.Rooms.get_room_by_id(room_id)\n    Beef.Users.set_user_left_current_room(user_id)\n    new_people_list = Enum.filter(room.peoplePreviewList, fn x -> x.id != user_id end)\n\n    decrement_room_people_count(\n      room.id,\n      new_people_list\n    )\n  end\n\n  # trusts that the user is in the room\n  def leave_room(user_id, room_id) do\n    room = Beef.Rooms.get_room_by_id(room_id)\n\n    if not is_nil(room) do\n      if room.numPeopleInside <= 1 do\n        delete_room_by_id(room.id)\n        {:bye, room}\n      else\n        Beef.Users.set_user_left_current_room(user_id)\n        new_people_list = Enum.filter(room.peoplePreviewList, fn x -> x.id != user_id end)\n\n        if room.creatorId != user_id do\n          decrement_room_people_count(\n            room.id,\n            new_people_list\n          )\n        else\n          newCreator = Beef.Rooms.get_next_creator_for_room(room.id)\n\n          if newCreator do\n            set_room_owner_and_dec(room.id, newCreator.id, new_people_list)\n            {:new_creator_id, newCreator.id}\n          else\n            delete_room_by_id(room.id)\n            {:bye, room}\n          end\n        end\n      end\n    end\n  end\n\n  def raw_insert(data, peoplePreviewList) do\n    %Room{peoplePreviewList: peoplePreviewList}\n    |> Room.insert_changeset(data)\n    |> Repo.insert(returning: true)\n  end\n\n  def update_name(user_id, name) do\n    from(r in Room,\n      where: r.creatorId == ^user_id,\n      update: [\n        set: [\n          name: ^name\n        ]\n      ]\n    )\n    |> Repo.update_all([])\n  end\n\n  @spec create(:invalid | %{optional(:__struct__) => none, optional(atom | binary) => any}) :: any\n  def create(data) do\n    user = Beef.Users.get_by_id(data.creatorId)\n\n    peoplePreviewList = [\n      %{\n        id: user.id,\n        displayName: user.displayName,\n        numFollowers: user.numFollowers,\n        avatarUrl: user.avatarUrl\n      }\n    ]\n\n    resp = raw_insert(data, peoplePreviewList)\n\n    resp =\n      case resp do\n        {:error, %{errors: [{:creatorId, {\"has already been taken\", _}}]}} ->\n          raw_insert(data, peoplePreviewList)\n\n        _ ->\n          resp\n      end\n\n    case resp do\n      {:ok, room} ->\n        Beef.Users.set_current_room(data.creatorId, room.id)\n\n      _ ->\n        nil\n    end\n\n    resp\n  end\n\n  def edit(room_id, data) do\n    %Room{id: room_id}\n    |> Room.edit_changeset(data)\n    |> Repo.update()\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/mutations/user_blocks.ex",
    "content": "defmodule Beef.Mutations.UserBlocks do\n  @moduledoc \"\"\"\n    DB Mutation functions for UserBlocks\n  \"\"\"\n  import Ecto.Query, warn: false\n\n  alias Beef.Schemas.UserBlock\n  alias Beef.Repo\n\n  def insert(data) do\n    %UserBlock{}\n    |> UserBlock.insert_changeset(data)\n    |> Repo.insert(on_conflict: :nothing)\n  end\n\n  def delete(user_id, user_id_to_block) do\n    from(ub in UserBlock, where: ub.userId == ^user_id and ub.userIdBlocked == ^user_id_to_block)\n    |> Beef.Repo.delete_all()\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/mutations/users.ex",
    "content": "defmodule Beef.Mutations.Users do\n  import Ecto.Query, warn: false\n\n  alias Beef.Repo\n  alias Beef.Schemas.User\n  alias Beef.Queries.Users, as: Query\n  alias Beef.RoomPermissions\n\n  def edit_profile(user_id, data) do\n    # TODO: make this not perform a db query\n    user_id\n    |> Beef.Users.get_by_id()\n    |> User.edit_changeset(data)\n    |> Repo.update()\n  end\n\n  def delete(user_id) do\n    %User{id: user_id} |> Repo.delete()\n  end\n\n  def bulk_insert(users) do\n    Repo.insert_all(\n      User,\n      users,\n      on_conflict: :nothing\n    )\n  end\n\n  def inc_num_following(user_id, n) do\n    Query.start()\n    |> Query.filter_by_id(user_id)\n    |> Query.inc_num_following_by_n(n)\n    |> Repo.update_all([])\n  end\n\n  def set_reason_for_ban(user_id, reason_for_ban) do\n    Query.start()\n    |> Query.filter_by_id(user_id)\n    |> Query.update_reason_for_ban(reason_for_ban)\n    |> Repo.update_all([])\n  end\n\n  def set_ip(user_id, ip) do\n    Query.start()\n    |> Query.filter_by_id(user_id)\n    |> Query.update_set_ip(ip)\n    |> Repo.update_all([])\n  end\n\n  def set_online(user_id) do\n    Query.start()\n    |> Query.filter_by_id(user_id)\n    |> Query.update_set_online_true()\n    |> Repo.update_all([])\n  end\n\n  def set_user_left_current_room(user_id) do\n    Onion.UserSession.set_current_room_id(user_id, nil)\n\n    Query.start()\n    |> Query.filter_by_id(user_id)\n    |> Query.update_set_current_room_nil()\n    |> Repo.update_all([])\n  end\n\n  def set_offline(user_id) do\n    Query.start()\n    |> Query.filter_by_id(user_id)\n    |> Query.update_set_online_false()\n    |> Query.update_set_last_online_to_now()\n    |> Repo.update_all([])\n  end\n\n  def set_current_room(user_id, room_id, can_speak \\\\ false, returning \\\\ false) do\n    roomPermissions =\n      case can_speak do\n        true ->\n          case RoomPermissions.set_speaker(user_id, room_id, true, true) do\n            {:ok, x} -> x\n            _ -> nil\n          end\n\n        _ ->\n          RoomPermissions.get(user_id, room_id)\n      end\n\n    Onion.UserSession.set_current_room_id(user_id, room_id)\n\n    q =\n      from(u in User,\n        where: u.id == ^user_id,\n        update: [\n          set: [\n            currentRoomId: ^room_id\n          ]\n        ]\n      )\n\n    q = if returning, do: select(q, [u], u), else: q\n\n    case Repo.update_all(q, []) do\n      {_, [user]} -> %{user | roomPermissions: roomPermissions}\n      _ -> nil\n    end\n  end\n\n  def twitter_find_or_create(user) do\n    db_user =\n      from(u in User,\n        where: u.twitterId == ^user.twitterId,\n        limit: 1\n      )\n      |> Repo.one()\n\n    if db_user do\n      if is_nil(db_user.twitterId) do\n        from(u in User,\n          where: u.id == ^db_user.id,\n          update: [\n            set: [\n              twitterId: ^user.twitterId\n            ]\n          ]\n        )\n        |> Repo.update_all([])\n      end\n\n      {:find, db_user}\n    else\n      {:create,\n       Repo.insert!(\n         %User{\n           username: Kousa.Utils.Random.big_ascii_id(),\n           email: if(user.email == \"\", do: nil, else: user.email),\n           twitterId: user.twitterId,\n           avatarUrl: user.avatarUrl,\n           bannerUrl: user.bannerUrl,\n           displayName:\n             if(is_nil(user.displayName) or String.trim(user.displayName) == \"\",\n               do: \"Novice Doge\",\n               else: user.displayName\n             ),\n           bio: user.bio,\n           hasLoggedIn: true\n         },\n         returning: true\n       )}\n    end\n  end\n\n  def github_find_or_create(user, github_access_token) do\n    githubId = Integer.to_string(user[\"id\"])\n\n    db_user =\n      from(u in User,\n        where: u.githubId == ^githubId,\n        limit: 1\n      )\n      |> Repo.one()\n\n    if db_user do\n      if is_nil(db_user.githubId) do\n        from(u in User,\n          where: u.id == ^db_user.id,\n          update: [\n            set: [\n              githubId: ^githubId,\n              githubAccessToken: ^github_access_token\n            ]\n          ]\n        )\n        |> Repo.update_all([])\n      end\n\n      {:find, db_user}\n    else\n      {:create,\n       Repo.insert!(\n         %User{\n           username: Kousa.Utils.Random.big_ascii_id(),\n           githubId: githubId,\n           email: if(user[\"email\"] == \"\", do: nil, else: user[\"email\"]),\n           githubAccessToken: github_access_token,\n           avatarUrl: user[\"avatar_url\"],\n           bannerUrl: user[\"banner_url\"],\n           displayName:\n             if(is_nil(user[\"name\"]) or String.trim(user[\"name\"]) == \"\",\n               do: \"Novice Doge\",\n               else: user[\"name\"]\n             ),\n           bio: user[\"bio\"],\n           hasLoggedIn: true\n         },\n         returning: true\n       )}\n    end\n  end\n\n  def discord_find_or_create(user, discord_access_token) do\n    discordId = user[\"id\"]\n\n    db_user =\n      from(u in User,\n        where: u.discordId == ^discordId,\n        limit: 1\n      )\n      |> Repo.one()\n\n    if db_user do\n      if is_nil(db_user.discordId) do\n        from(u in User,\n          where: u.id == ^db_user.id,\n          update: [\n            set: [\n              discordId: ^discordId,\n              discordAccessToken: ^discord_access_token\n            ]\n          ]\n        )\n        |> Repo.update_all([])\n      end\n\n      {:find, db_user}\n    else\n      {:create,\n       Repo.insert!(\n         %User{\n           username: Kousa.Utils.Random.big_ascii_id(),\n           discordId: discordId,\n           email: if(user[\"email\"] == \"\", do: nil, else: user[\"email\"]),\n           discordAccessToken: discord_access_token,\n           avatarUrl: Kousa.Discord.get_avatar_url(user),\n           displayName: user[\"username\"],\n           hasLoggedIn: true\n         },\n         returning: true\n       )}\n    end\n  end\n\n  def create_bot(owner_id, username) do\n    %User{}\n    |> User.edit_changeset(%{\n      id: Ecto.UUID.generate(),\n      username: username,\n      # @todo pick better default\n      avatarUrl: \"https://pbs.twimg.com/profile_images/1384417471944290304/4epg3HTW_400x400.jpg\",\n      displayName: username,\n      botOwnerId: owner_id,\n      bio: \"I am a bot\",\n      apiKey: Ecto.UUID.generate()\n    })\n    |> Repo.insert(returning: true)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/queries/rooms.ex",
    "content": "defmodule Beef.Queries.Rooms do\n  import Ecto.Query\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n\n  def start do\n    from(r in Room)\n  end\n\n  def userStart do\n    from(u in User)\n  end\n\n  def filter_by_current_room_id(query, room_id) do\n    where(query, [u], u.currentRoomId == ^room_id)\n  end\n\n  def filter_by_creator_id(query, creator_id) do\n    where(query, [r], r.creatorId == ^creator_id)\n  end\n\n  def filter_by_room_id_and_creator_id(query, room_id, user_id) do\n    where(query, [r], r.id == ^room_id and r.creatorId == ^user_id)\n  end\n\n  def limit_one(query) do\n    limit(query, [r], 1)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/queries/user_blocks.ex",
    "content": "defmodule Beef.Queries.UserBlocks do\n  @moduledoc \"\"\"\n  Query builder functions for UserBlocks\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Beef.Schemas.UserBlock\n  alias Beef.Schemas.User\n\n  def start do\n    from(ub in UserBlock)\n  end\n\n  def filter_by_id_and_blockedId(query, user_id, user_id_blockedId) do\n    where(query, [ub], ub.userId == ^user_id and ub.userIdBlocked == ^user_id_blockedId)\n  end\n\n  def filter_by_username_and_blockedId(query, username, user_id_blocked) do\n    query\n    |> join(:inner, [ub], u in User, on: u.id == ub.userId)\n    |> where([ub, u], ub.userIdBlocked == ^user_id_blocked and u.username == ^username)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/queries/users.ex",
    "content": "defmodule Beef.Queries.Users do\n  @moduledoc \"\"\"\n  all functions in this module should be \"Query builder\" functions,\n  they should not touch the database.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Follow\n  alias Beef.Schemas.UserBlock\n\n  def start do\n    from(u in User)\n  end\n\n  def limit_one(query) do\n    limit(query, [], 1)\n  end\n\n  def they_blocked_me_info(query, me_id) do\n    query\n    |> join(:left, [u], ub in UserBlock,\n      as: :they_blocked,\n      on: ub.userId == u.id and ub.userIdBlocked == ^me_id\n    )\n    |> select_merge([they_blocked: ub], %{\n      theyBlockedMe: not is_nil(ub.userId)\n    })\n  end\n\n  def i_blocked_them_info(query, me_id) do\n    query\n    |> join(:left, [u], ub in UserBlock,\n      as: :i_blocked,\n      on: ub.userIdBlocked == u.id and ub.userId == ^me_id\n    )\n    |> select_merge([i_blocked: ub], %{\n      iBlockedThem: not is_nil(ub.userId)\n    })\n  end\n\n  def follow_info(query, me_id) do\n    query\n    |> join(:left, [u], f_i_follow_them in Follow,\n      as: :i_follow,\n      on: f_i_follow_them.userId == u.id and f_i_follow_them.followerId == ^me_id\n    )\n    |> join(:left, [u], f_they_follow_me in Follow,\n      as: :they_follow,\n      on: f_they_follow_me.userId == ^me_id and f_they_follow_me.followerId == u.id\n    )\n    |> select_merge([i_follow: f_i_follow_them, they_follow: f_they_follow_me], %{\n      followsYou: not is_nil(f_they_follow_me.userId),\n      youAreFollowing: not is_nil(f_i_follow_them.userId)\n    })\n  end\n\n  def filter_by_github_ids(query, github_ids) do\n    where(query, [u], u.githubId in ^github_ids)\n  end\n\n  def select_id(query) do\n    select(query, [u], u.id)\n  end\n\n  def filter_by_id(query, user_id) do\n    where(query, [u], u.id == ^user_id)\n  end\n\n  def filter_by_username(query, username) do\n    where(query, [u], u.username == ^username)\n  end\n\n  def inc_num_following_by_n(query, n) do\n    update(query,\n      inc: [\n        numFollowing: ^n\n      ]\n    )\n  end\n\n  def update_reason_for_ban(query, reason_for_ban) do\n    update(query,\n      set: [\n        reasonForBan: ^reason_for_ban\n      ]\n    )\n  end\n\n  def update_set_ip(query, ip) do\n    update(query,\n      set: [\n        ip: ^ip\n      ]\n    )\n  end\n\n  def update_set_online_true(query) do\n    update(query,\n      set: [\n        online: true\n      ]\n    )\n  end\n\n  def update_set_online_false(query) do\n    update(query,\n      set: [\n        online: false\n      ]\n    )\n  end\n\n  def update_set_last_online_to_now(query) do\n    update(query,\n      set: [\n        lastOnline: fragment(\"now()\")\n      ]\n    )\n  end\n\n  def update_set_current_room_nil(query) do\n    update(query,\n      set: [\n        currentRoomId: nil\n      ]\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/room_blocks.ex",
    "content": "defmodule Beef.RoomBlocks do\n  import Ecto.Query\n  alias Kousa.Utils.Pagination\n  alias Beef.Schemas.User\n  alias Beef.Schemas.RoomBlock\n  alias Beef.Repo\n  alias Beef.Users\n\n  def unban(room_id, user_id) do\n    from(rb in RoomBlock, where: rb.userId == ^user_id and rb.roomId == ^room_id)\n    |> Repo.delete_all()\n  end\n\n  def blocked?(room_id, user_id) do\n    ip = Users.get_ip(user_id)\n\n    not is_nil(\n      from(rb in RoomBlock,\n        where: (rb.userId == ^user_id or rb.ip == ^ip) and rb.roomId == ^room_id,\n        limit: 1\n      )\n      |> Beef.Repo.one()\n    )\n  end\n\n  @fetch_limit 31\n\n  def get_blocked_users(room_id, offset) do\n    from(u in User,\n      inner_join: rb in RoomBlock,\n      on: u.id == rb.userId,\n      where: rb.roomId == ^room_id,\n      offset: ^offset,\n      limit: @fetch_limit\n    )\n    |> Beef.Repo.all()\n    |> Pagination.items_to_offset_tuple(offset, @fetch_limit)\n  end\n\n  @spec insert(:invalid | %{optional(:__struct__) => none, optional(atom | binary) => any}) :: any\n  def insert(data) do\n    %RoomBlock{}\n    |> RoomBlock.insert_changeset(data)\n    |> Beef.Repo.insert(on_conflict: :nothing)\n  end\n\n  def upsert(data = %{ip: ip}) do\n    %RoomBlock{}\n    |> RoomBlock.insert_changeset(data)\n    |> Beef.Repo.insert(\n      on_conflict: [set: [ip: ip]],\n      conflict_target: [:userId, :roomId]\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/room_permissions.ex",
    "content": "defmodule Beef.RoomPermissions do\n  import Ecto.Query\n\n  def insert(data) do\n    %Beef.Schemas.RoomPermission{}\n    |> Beef.Schemas.RoomPermission.insert_changeset(data)\n    |> Beef.Repo.insert(on_conflict: :nothing)\n  end\n\n  def upsert(data, set, returning \\\\ true) do\n    %Beef.Schemas.RoomPermission{}\n    |> Beef.Schemas.RoomPermission.insert_changeset(data)\n    |> Beef.Repo.insert(\n      on_conflict: [set: set],\n      conflict_target: [:userId, :roomId],\n      returning: returning\n    )\n  end\n\n  def speaker?(user_id, room_id) do\n    not is_nil(\n      Beef.Repo.one(\n        from(rp in Beef.Schemas.RoomPermission,\n          where: rp.roomId == ^room_id and rp.userId == ^user_id and rp.isSpeaker == true\n        )\n      )\n    )\n  end\n\n  def listener?(user_id, room_id) do\n    not speaker?(user_id, room_id)\n  end\n\n  def mod?(user_id, room_id) do\n    not is_nil(\n      Beef.Repo.one(\n        from(rp in Beef.Schemas.RoomPermission,\n          where: rp.roomId == ^room_id and rp.userId == ^user_id and rp.isMod == true\n        )\n      )\n    )\n  end\n\n  def asked_to_speak?(user_id, room_id) do\n    not is_nil(\n      Beef.Repo.one(\n        from(rp in Beef.Schemas.RoomPermission,\n          where: rp.roomId == ^room_id and rp.userId == ^user_id and rp.askedToSpeak == true\n        )\n      )\n    )\n  end\n\n  def get(user_id, room_id) do\n    from(rp in Beef.Schemas.RoomPermission,\n      where: rp.userId == ^user_id and rp.roomId == ^room_id,\n      limit: 1\n    )\n    |> Beef.Repo.one()\n  end\n\n  def ask_to_speak(user_id, room_id) do\n    upsert(%{roomId: room_id, userId: user_id, askedToSpeak: true}, askedToSpeak: true)\n  end\n\n  def set_speaker(user_id, room_id, speaker?, returning \\\\ false) do\n    upsert(\n      %{roomId: room_id, userId: user_id, isSpeaker: speaker?},\n      [isSpeaker: speaker?],\n      returning\n    )\n  end\n\n  def set_is_mod(user_id, room_id, is_mod) do\n    upsert(\n      %{roomId: room_id, userId: user_id, isMod: is_mod},\n      [isMod: is_mod],\n      false\n    )\n  end\n\n  def make_listener(user_id, room_id) do\n    upsert(\n      %{roomId: room_id, userId: user_id, isSpeaker: false, askedToSpeak: false},\n      [isSpeaker: false, askedToSpeak: false],\n      false\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/rooms.ex",
    "content": "defmodule Beef.Rooms do\n  @moduledoc \"\"\"\n  Empty context module for Rooms\n  \"\"\"\n\n  # ACCESS functions\n  defdelegate get_room_status(user_id), to: Beef.Access.Rooms\n  defdelegate can_join_room(room_id, user_id), to: Beef.Access.Rooms\n  defdelegate get_top_public_rooms(user_id, offset \\\\ 0), to: Beef.Access.Rooms\n  defdelegate get_room_by_id(room_id), to: Beef.Access.Rooms\n  defdelegate get_next_creator_for_room(room_id), to: Beef.Access.Rooms\n  defdelegate get_a_user_for_room(room_id), to: Beef.Access.Rooms\n  defdelegate get_room_by_creator_id(creator_id), to: Beef.Access.Rooms\n  defdelegate owner?(room_id, user_id), to: Beef.Access.Rooms\n  defdelegate search_name(start_of_name), to: Beef.Access.Rooms\n  @spec all_rooms :: any\n  defdelegate all_rooms(), to: Beef.Access.Rooms\n\n  # MUTATION functions\n  defdelegate set_room_privacy_by_creator_id(user_id, isPrivate, new_name),\n    to: Beef.Mutations.Rooms\n\n  defdelegate replace_room_owner(user_id, new_creator_id), to: Beef.Mutations.Rooms\n  defdelegate join_room(room, user_id), to: Beef.Mutations.Rooms\n  defdelegate increment_room_people_count(room_id), to: Beef.Mutations.Rooms\n  defdelegate increment_room_people_count(room_id, new_people_list), to: Beef.Mutations.Rooms\n  defdelegate delete_room_by_id(room_id), to: Beef.Mutations.Rooms\n  defdelegate decrement_room_people_count(room_id, new_people_list), to: Beef.Mutations.Rooms\n  defdelegate set_room_owner_and_dec(room_id, user_id, new_people_list), to: Beef.Mutations.Rooms\n  defdelegate kick_from_room(user_id, room_id), to: Beef.Mutations.Rooms\n  defdelegate leave_room(user_id, room_id), to: Beef.Mutations.Rooms\n  defdelegate raw_insert(data, peoplePreviewList), to: Beef.Mutations.Rooms\n  defdelegate update_name(user_id, name), to: Beef.Mutations.Rooms\n  defdelegate create(data), to: Beef.Mutations.Rooms\n  defdelegate edit(room_id, data), to: Beef.Mutations.Rooms\nend\n"
  },
  {
    "path": "kousa/lib/beef/scheduled_rooms.ex",
    "content": "defmodule Beef.ScheduledRooms do\n  import Ecto.Query\n  import Ecto.Changeset\n  alias Kousa.Utils.Pagination\n  alias Beef.Schemas.ScheduledRoom\n  alias Beef.Repo\n\n  @fetch_limit 16\n\n  def get_by_id(id) do\n    from(sr in ScheduledRoom,\n      where: sr.id == ^id,\n      inner_join: u in assoc(sr, :creator),\n      preload: [\n        creator: u\n      ]\n    )\n    |> Repo.one()\n  end\n\n  def delete(user_id, id) do\n    from(sr in ScheduledRoom, where: sr.creatorId == ^user_id and sr.id == ^id)\n    |> Repo.delete_all()\n  end\n\n  def insert(data) do\n    %ScheduledRoom{} |> ScheduledRoom.insert_changeset(data) |> Repo.insert(returning: true)\n  end\n\n  def room_started(user_id, id, room_id) do\n    from(sr in ScheduledRoom,\n      where: sr.creatorId == ^user_id and sr.id == ^id,\n      update: [\n        set: [\n          roomId: ^room_id,\n          started: true\n        ]\n      ]\n    )\n    |> Repo.update_all([])\n  end\n\n  @spec edit(\n          any,\n          any,\n          :invalid | %{optional(:__struct__) => none, optional(atom | binary) => any}\n        ) :: :ok | {:error, Ecto.Changeset.t()}\n  def edit(user_id, id, data) do\n    case ScheduledRoom.edit_changeset(%ScheduledRoom{}, data) |> apply_action(:update) do\n      {:ok, cleaned_data} ->\n        from(sr in ScheduledRoom,\n          where: sr.creatorId == ^user_id and sr.id == ^id,\n          update: [\n            set: [\n              name: ^cleaned_data.name,\n              description: ^cleaned_data.description,\n              scheduledFor: ^cleaned_data.scheduledFor\n            ]\n          ]\n        )\n        |> Repo.update_all([])\n\n        :ok\n\n      error ->\n        error\n    end\n  end\n\n  def add_cursor(q, \"\") do\n    q\n  end\n\n  def add_cursor(q, nil) do\n    q\n  end\n\n  def add_cursor(q, cursor) do\n    with [iso, id] <- String.split(cursor, \"|\"),\n         {:ok, dt} <- Timex.parse(iso, \"{ISO:Basic:Z}\") do\n      where(q, [sr], {^dt, ^id} < {sr.scheduledFor, sr.id})\n    else\n      _ ->\n        q\n    end\n  end\n\n  def get_my_scheduled_rooms_about_to_start(user_id) do\n    from(sr in ScheduledRoom,\n      inner_join: u in assoc(sr, :creator),\n      preload: [\n        creator: u\n      ],\n      where:\n        sr.creatorId == ^user_id and is_nil(sr.roomId) and\n          sr.started ==\n            false and\n          fragment(\n            \"? - interval '1 hours' < now() and ? + interval '2 hours' > now()\",\n            sr.scheduledFor,\n            sr.scheduledFor\n          ),\n      order_by: [asc: sr.scheduledFor],\n      limit: ^@fetch_limit\n    )\n    |> Repo.all()\n  end\n\n  @spec get_feed(String.t(), boolean(), String.t()) :: {[ScheduledRoom], nil | number}\n  def get_feed(user_id, get_only_my_scheduled_rooms, cursor) do\n    q =\n      from(sr in ScheduledRoom,\n        inner_join: u in assoc(sr, :creator),\n        order_by: [asc: sr.scheduledFor, asc: sr.id],\n        where: sr.started == false,\n        limit: ^@fetch_limit,\n        preload: [\n          creator: u\n        ]\n      )\n\n    get_only_my_scheduled_rooms\n    |> if(\n      do:\n        where(\n          q,\n          [sr],\n          sr.creatorId == ^user_id and sr.scheduledFor > fragment(\"now() - interval '2 hours'\")\n        ),\n      else: where(q, [sr], sr.scheduledFor > fragment(\"now()\"))\n    )\n    |> add_cursor(cursor)\n    |> Repo.all()\n    |> Pagination.items_to_cursor_tuple(\n      @fetch_limit,\n      &(Timex.format!(&1.scheduledFor, \"{ISO:Basic:Z}\") <> \"|\" <> &1.id)\n    )\n  end\n\n  @spec get_mine(String.t()) :: ScheduledRoom | nil\n  def get_mine(user_id) do\n    from(sr in ScheduledRoom,\n      inner_join: u in assoc(sr, :creator),\n      on: sr.creatorId == u.id,\n      where: sr.scheduledFor > fragment(\"now()\") and sr.creatorId == ^user_id,\n      limit: 1,\n      preload: [\n        creator: u\n      ]\n    )\n    |> Repo.one()\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/attending_scheduled_room.ex",
    "content": "defmodule Beef.Schemas.AttendingScheduledRoom do\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Beef.Schemas.User\n  alias Beef.Schemas.ScheduledRoom\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  @type t :: %__MODULE__{\n          userId: Ecto.UUID.t(),\n          scheduledRoomId: Ecto.UUID.t()\n        }\n\n  @primary_key false\n  schema \"attending_scheduled_rooms\" do\n    belongs_to(:user, User, foreign_key: :userId, type: :binary_id)\n    belongs_to(:scheduledRoom, ScheduledRoom, foreign_key: :scheduledRoomId, type: :binary_id)\n\n    timestamps()\n  end\n\n  @doc false\n  def insert_changeset(follow, attrs) do\n    follow\n    |> cast(attrs, [:userId, :scheduledRoomId])\n    |> validate_required([:userId, :scheduledRoomId])\n    |> unique_constraint(:already_attending, name: \"attending_scheduled_rooms_pkey\")\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/follow.ex",
    "content": "defmodule Beef.Schemas.Follow do\n  use Ecto.Schema\n  import Ecto.Changeset\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  alias Beef.Schemas.User\n\n  @type t :: %__MODULE__{\n          userId: Ecto.UUID.t(),\n          followerId: Ecto.UUID.t()\n        }\n\n  @primary_key false\n  schema \"followers\" do\n    # person who is being followed\n    belongs_to(:user, User, foreign_key: :userId, type: :binary_id)\n    # person who is following\n    belongs_to(:follower, User, foreign_key: :followerId, type: :binary_id)\n\n    timestamps()\n  end\n\n  @doc false\n  def insert_changeset(follow, attrs) do\n    follow\n    |> cast(attrs, [:userId, :followerId])\n    |> validate_required([:userId, :followerId])\n    |> unique_constraint(:already_following, name: \"followers_pkey\")\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/room.ex",
    "content": "defmodule Beef.Schemas.Room do\n  use Ecto.Schema\n  use Broth.Message.Push\n  import Ecto.Changeset\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  alias Beef.Schemas.User\n  @type chatMode :: :default | :disabled | :follower_only\n  @type t :: %__MODULE__{\n          id: Ecto.UUID.t(),\n          name: String.t(),\n          description: String.t(),\n          numPeopleInside: integer(),\n          isPrivate: boolean(),\n          user: User.t() | Ecto.Association.NotLoaded.t(),\n          peoplePreviewList: [User.Preview.t()]\n        }\n\n  @derive {Jason.Encoder,\n           only:\n             ~w(id name description numPeopleInside isPrivate chatMode chatThrottle autoSpeaker\n           creatorId peoplePreviewList voiceServerId inserted_at)a}\n\n  @primary_key {:id, :binary_id, []}\n  schema \"rooms\" do\n    field(:name, :string)\n    field(:description, :string, default: \"\")\n    field(:numPeopleInside, :integer)\n    field(:isPrivate, :boolean)\n    field(:voiceServerId, :string)\n    field(:autoSpeaker, :boolean, default: false)\n    field(:chatThrottle, :integer, default: 1000)\n    field(:chatMode, Ecto.Enum, values: [:default, :disabled, :follower_only])\n\n    # TODO: change this to creator!\n    belongs_to(:user, User, foreign_key: :creatorId, type: :binary_id)\n    embeds_many(:peoplePreviewList, User.Preview)\n\n    timestamps()\n  end\n\n  @spec insert_changeset(\n          {map, map} | %{:__struct__ => atom | %{__changeset__: map}, optional(atom) => any},\n          :invalid | %{optional(:__struct__) => none, optional(atom | binary) => any}\n        ) :: Ecto.Changeset.t()\n  @doc false\n  def insert_changeset(room, attrs) do\n    room\n    |> cast(attrs, [\n      :id,\n      :name,\n      :creatorId,\n      :isPrivate,\n      :numPeopleInside,\n      :voiceServerId,\n      :description,\n      :chatMode,\n      :chatThrottle,\n      :autoSpeaker\n    ])\n    |> validate_required([:name, :creatorId])\n    |> validate_length(:name, min: 2, max: 60)\n    |> validate_length(:description, max: 500)\n    |> unique_constraint(:creatorId)\n  end\n\n  @spec edit_changeset(\n          {map, map} | %{:__struct__ => atom | %{__changeset__: map}, optional(atom) => any},\n          :invalid | %{optional(:__struct__) => none, optional(atom | binary) => any}\n        ) :: Ecto.Changeset.t()\n  def edit_changeset(room, attrs) do\n    room\n    |> cast(attrs, [:id, :name, :isPrivate, :description, :autoSpeaker, :chatMode, :chatThrottle])\n    |> validate_length(:name, min: 2, max: 60)\n    |> validate_number(:chatThrottle, greater_than_or_equal_to: 0)\n    |> validate_length(:description, max: 500)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/room_block.ex",
    "content": "defmodule Beef.Schemas.RoomBlock do\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  @primary_key false\n  schema \"room_blocks\" do\n    belongs_to(:user, User, foreign_key: :userId, type: :binary_id)\n    belongs_to(:room, Room, foreign_key: :roomId, type: :binary_id)\n    belongs_to(:mod, User, foreign_key: :modId, type: :binary_id)\n    field(:ip, :string, null: true)\n\n    timestamps()\n  end\n\n  @doc false\n  def insert_changeset(roomBlock, attrs) do\n    roomBlock\n    |> cast(attrs, [:userId, :roomId, :modId, :ip])\n    |> validate_required([:userId, :roomId, :modId])\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/room_permission.ex",
    "content": "defmodule Beef.Schemas.RoomPermission do\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Beef.Schemas.Room\n\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  @type t :: %__MODULE__{\n          roomId: Ecto.UUID.t(),\n          userId: Ecto.UUID.t(),\n          isSpeaker: boolean(),\n          isMod: boolean(),\n          askedToSpeak: boolean()\n        }\n\n  alias Beef.Schemas.User\n\n  @derive {Jason.Encoder, only: [:isSpeaker, :isMod, :askedToSpeak]}\n  @primary_key false\n  schema \"room_permissions\" do\n    belongs_to(:user, User, foreign_key: :userId, type: :binary_id)\n    belongs_to(:room, Room, foreign_key: :roomId, type: :binary_id)\n    field(:isSpeaker, :boolean)\n    field(:isMod, :boolean)\n    field(:askedToSpeak, :boolean)\n\n    timestamps()\n  end\n\n  @doc false\n  def insert_changeset(roomPerm, attrs) do\n    roomPerm\n    |> cast(attrs, [:userId, :roomId, :isSpeaker, :isMod, :askedToSpeak])\n    |> validate_required([:userId, :roomId])\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/scheduled_room.ex",
    "content": "defmodule Beef.Schemas.ScheduledRoom do\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Beef.Room\n  alias Beef.Schemas.User\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  @type t :: %__MODULE__{\n          id: Ecto.UUID.t(),\n          name: String.t(),\n          numAttending: integer(),\n          scheduledFor: DateTime.t()\n        }\n\n  @derive {Jason.Encoder,\n           only: [\n             :id,\n             :name,\n             :numAttending,\n             :scheduledFor,\n             :description,\n             :roomId,\n             :creator,\n             :creatorId\n           ]}\n\n  @primary_key {:id, :binary_id, []}\n  schema \"scheduled_rooms\" do\n    field(:name, :string)\n    field(:numAttending, :integer)\n    field(:scheduledFor, :utc_datetime_usec)\n    field(:description, :string, default: \"\")\n    field(:started, :boolean)\n\n    belongs_to(:creator, User, foreign_key: :creatorId, type: :binary_id)\n    belongs_to(:room, Room, foreign_key: :roomId, type: :binary_id)\n\n    timestamps()\n  end\n\n  def validate_future_date(%{changes: changes} = changeset, field) do\n    if date = changes[field] do\n      if DateTime.compare(date, DateTime.utc_now()) == :gt do\n        changeset\n      else\n        changeset\n        |> add_error(field, \"Date needs to be in the future\")\n      end\n    else\n      changeset\n    end\n  end\n\n  def validate_not_too_far_into_future_date(%{changes: changes} = changeset, field) do\n    if date = changes[field] do\n      # 1 extra day to avoid conflicts with frontend\n      max_date = DateTime.utc_now() |> Timex.shift(months: 6, days: 1)\n\n      if DateTime.compare(date, max_date) == :lt do\n        changeset\n      else\n        changeset\n        |> add_error(field, \"Date can't be further than 6 month in advance\")\n      end\n    else\n      changeset\n    end\n  end\n\n  def common_validation(attrs) do\n    attrs\n    |> validate_future_date(:scheduledFor)\n    |> validate_not_too_far_into_future_date(:scheduledFor)\n    |> validate_length(:name, min: 2, max: 60)\n    |> validate_length(:description, max: 200)\n  end\n\n  @doc false\n  def insert_changeset(room, attrs) do\n    room\n    |> cast(attrs, [:id, :name, :creatorId, :scheduledFor, :description])\n    |> validate_required([:name, :creatorId, :scheduledFor])\n    |> common_validation()\n  end\n\n  def edit_changeset(room, attrs) do\n    room\n    |> cast(attrs, [:id, :name, :scheduledFor, :description])\n    |> validate_required([:name, :scheduledFor])\n    |> common_validation()\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/scheduled_room_cohost.ex",
    "content": "defmodule Beef.Schemas.ScheduledRoomCohost do\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Beef.Schemas.User\n  alias Beef.Schemas.ScheduledRoom\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  @type t :: %__MODULE__{\n          userId: Ecto.UUID.t(),\n          scheduledRoomId: Ecto.UUID.t()\n        }\n\n  @primary_key false\n  schema \"scheduled_room_cohosts\" do\n    belongs_to(:user, User, foreign_key: :userId, type: :binary_id)\n    belongs_to(:scheduledRoom, ScheduledRoom, foreign_key: :scheduledRoomId, type: :binary_id)\n\n    timestamps()\n  end\n\n  @doc false\n  def insert_changeset(follow, attrs) do\n    follow\n    |> cast(attrs, [:userId, :scheduledRoomId])\n    |> validate_required([:userId, :scheduledRoomId])\n    |> unique_constraint(:already_cohost, name: \"scheduled_room_cohosts_pkey\")\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/user.ex",
    "content": "defmodule Beef.Schemas.User do\n  use Ecto.Schema\n\n  # the struct defined here can also be pushed to the user\n  use Broth.Message.Push\n  import Ecto.Changeset\n  alias Beef.Schemas.Room\n\n  defmodule Preview do\n    use Ecto.Schema\n\n    # TODO: Make this a separate Schema that sees the same table.\n\n    @derive {Jason.Encoder, only: [:id, :displayName, :numFollowers, :avatarUrl]}\n\n    @primary_key false\n    embedded_schema do\n      # does User.Preview really need an id?\n      field(:id, :binary_id)\n\n      field(:displayName, :string)\n      field(:numFollowers, :integer)\n      field(:avatarUrl, :string)\n    end\n  end\n\n  @timestamps_opts [type: :utc_datetime_usec]\n  @type whisperPrivacySetting :: :on | :off\n  @type t :: %__MODULE__{\n          id: Ecto.UUID.t(),\n          twitterId: String.t(),\n          githubId: String.t(),\n          discordId: String.t(),\n          username: String.t(),\n          email: String.t(),\n          githubAccessToken: String.t(),\n          discordAccessToken: String.t(),\n          displayName: String.t(),\n          avatarUrl: String.t(),\n          bannerUrl: String.t(),\n          whisperPrivacySetting: whisperPrivacySetting(),\n          bio: String.t(),\n          reasonForBan: String.t(),\n          ip: nil | String.t(),\n          tokenVersion: integer(),\n          numFollowing: integer(),\n          numFollowers: integer(),\n          hasLoggedIn: boolean(),\n          online: boolean(),\n          contributions: integer(),\n          staff: boolean(),\n          lastOnline: DateTime.t(),\n          youAreFollowing: nil | boolean(),\n          followsYou: nil | boolean(),\n          botOwnerId: nil | Ecto.UUID.t(),\n          roomPermissions: nil | Beef.Schemas.RoomPermission.t(),\n          currentRoomId: Ecto.UUID.t(),\n          currentRoom: Room.t() | Ecto.Association.NotLoaded.t()\n        }\n\n  @primary_key {:id, :binary_id, []}\n  schema \"users\" do\n    field(:githubId, :string)\n    field(:twitterId, :string)\n    field(:discordId, :string)\n    field(:username, :string)\n    field(:email, :string)\n    field(:githubAccessToken, :string)\n    field(:discordAccessToken, :string)\n    field(:displayName, :string)\n    field(:avatarUrl, :string)\n    field(:bannerUrl, :string)\n    field(:bio, :string, default: \"\")\n    field(:reasonForBan, :string)\n    field(:tokenVersion, :integer)\n    field(:numFollowing, :integer)\n    field(:numFollowers, :integer)\n    field(:hasLoggedIn, :boolean)\n    field(:online, :boolean)\n    field(:contributions, :integer)\n    field(:staff, :boolean)\n    field(:lastOnline, :utc_datetime_usec)\n    field(:youAreFollowing, :boolean, virtual: true)\n    field(:followsYou, :boolean, virtual: true)\n    field(:roomPermissions, :map, virtual: true, null: true)\n    field(:muted, :boolean, virtual: true)\n    field(:deafened, :boolean, virtual: true)\n    field(:apiKey, :binary_id)\n    field(:ip, :string, null: true)\n    field(:theyBlockedMe, :boolean, virtual: true)\n    field(:iBlockedThem, :boolean, virtual: true)\n    field(:whisperPrivacySetting, Ecto.Enum, values: [:on, :off])\n\n    belongs_to(:botOwner, Beef.Schemas.User, foreign_key: :botOwnerId, type: :binary_id)\n    belongs_to(:currentRoom, Room, foreign_key: :currentRoomId, type: :binary_id)\n\n    many_to_many(:blocked_by, __MODULE__,\n      join_through: \"user_blocks\",\n      join_keys: [userIdBlocked: :id, userId: :id]\n    )\n\n    many_to_many(:blocking, __MODULE__,\n      join_through: \"user_blocks\",\n      join_keys: [userId: :id, userIdBlocked: :id]\n    )\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(user, attrs) do\n    # TODO: amend this to accept *either* githubId or twitterId and also\n    # pipe edit_changeset into this puppy.\n    user\n    |> cast(attrs, ~w(username githubId avatarUrl bannerUrl)a)\n    |> validate_required([:username, :githubId, :avatarUrl, :bannerUrl])\n  end\n\n  def api_key_changeset(user, attrs) do\n    user\n    |> cast(attrs, [\n      :apiKey\n    ])\n  end\n\n  def admin_update_changeset(user, attrs) do\n    user\n    |> cast(attrs, [\n      :staff,\n      :contributions\n    ])\n  end\n\n  def edit_changeset(user, attrs) do\n    user\n    |> cast(attrs, [\n      :id,\n      :username,\n      :bio,\n      :displayName,\n      :avatarUrl,\n      :bannerUrl,\n      :apiKey,\n      :botOwnerId\n    ])\n    |> validate_required([:username, :displayName, :avatarUrl])\n    |> update_change(:displayName, &String.trim/1)\n    |> validate_length(:bio, min: 0, max: 160)\n    |> validate_length(:displayName, min: 2, max: 50)\n    |> validate_format(:username, ~r/^[\\w\\.]{4,15}$/)\n    |> validate_format(\n      :avatarUrl,\n      ~r/^https?:\\/\\/(www\\.|)((a|p)bs.twimg.com\\/(profile_images|sticky\\/default_profile_images)\\/(.*)\\.(jpg|png|jpeg|webp)|avatars\\.githubusercontent\\.com\\/u\\/[^\\s]+|github.com\\/identicons\\/[^\\s]+|cdn.discordapp.com\\/avatars\\/[^\\s]+\\/[^\\s]+\\.(jpg|png|jpeg|webp))/\n    )\n    |> validate_format(\n      :bannerUrl,\n      ~r/^https?:\\/\\/(www\\.|)(pbs.twimg.com\\/profile_banners\\/(.+)\\/(.+)(?:\\.(jpg|png|jpeg|webp))?|avatars\\.githubusercontent\\.com\\/u\\/)/\n    )\n    |> unique_constraint(:username)\n  end\n\n  defimpl Jason.Encoder do\n    @fields ~w(id whisperPrivacySetting username avatarUrl bannerUrl bio online contributions staff\n  lastOnline currentRoomId currentRoom displayName numFollowing numFollowers\n  youAreFollowing followsYou botOwnerId roomPermissions iBlockedThem)a\n\n    defp transform_current_room(fields = %{currentRoom: %Ecto.Association.NotLoaded{}}) do\n      Map.delete(fields, :currentRoom)\n    end\n\n    defp transform_current_room(fields), do: fields\n\n    def encode(user, opts) do\n      user\n      |> Map.take(@fields)\n      |> transform_current_room\n      |> Jason.Encoder.encode(opts)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/schemas/user_block.ex",
    "content": "defmodule Beef.Schemas.UserBlock do\n  use Ecto.Schema\n  import Ecto.Changeset\n  @timestamps_opts [type: :utc_datetime_usec]\n\n  alias Beef.Schemas.User\n\n  @primary_key false\n  schema \"user_blocks\" do\n    belongs_to(:user, User, foreign_key: :userId, type: :binary_id)\n    belongs_to(:blockedUser, User, foreign_key: :userIdBlocked, type: :binary_id)\n\n    timestamps()\n  end\n\n  @doc false\n  def insert_changeset(userBlock, attrs) do\n    userBlock\n    |> cast(attrs, [:userId, :userIdBlocked])\n    |> validate_required([:userId, :userIdBlocked])\n  end\nend\n"
  },
  {
    "path": "kousa/lib/beef/user_blocks.ex",
    "content": "defmodule Beef.UserBlocks do\n  @moduledoc \"\"\"\n  Empty context module for UserBlocks\n\n  This module will probably go away when User/Block relationships\n  are reconfigured to use Ecto associations\n  \"\"\"\n\n  # ACCESS\n  defdelegate blocked?(user_id, user_id_blocked), to: Beef.Access.UserBlocks\n  defdelegate username_blocked?(username, user_id_blocked), to: Beef.Access.UserBlocks\n\n  # MUTATIONS\n  defdelegate insert(data), to: Beef.Mutations.UserBlocks\n  defdelegate delete(user_id, user_id_blocked), to: Beef.Mutations.UserBlocks\nend\n"
  },
  {
    "path": "kousa/lib/beef/users.ex",
    "content": "defmodule Beef.Users do\n  @moduledoc \"\"\"\n  Context module for Users.  This module acts as a \"gateway\" module defining\n  the \"boundary\" for Users database access.  Consider Beef.Users.* modules\n  to be \"private modules\".  If in the future we would like to enforce these\n  boundary conditions at compile time, consider using Sasa Juric's Boundary\n  library:\n\n  https://hex.pm/packages/boundary\n\n  NB (5 Mar 2021): these functions are probably going to get streamlined =D\n  \"\"\"\n\n  # ACCESS functions\n  defdelegate get(user_id), to: Beef.Access.Users\n  # not implemented yet:\n  defdelegate get(user_id, opts), to: Beef.Access.Users\n\n  defdelegate find_by_github_ids(ids), to: Beef.Access.Users\n  defdelegate search(query, offset), to: Beef.Access.Users\n\n  #####################################################################################\n  # CHOPPING BLOCK\n  # we should strive to make the queries simpler and *reduce code*, so\n  # these functions are on the chopping block.  Strategy should be to query the get\n  # function and retrieve the data either from the fields or with a preload.\n  defdelegate get_by_id(user_id), to: Beef.Access.Users\n  defdelegate get_by_id_with_follow_info(me_id, them_id), to: Beef.Access.Users\n  defdelegate get_by_id_with_room_permissions(user_id), to: Beef.Access.Users\n  defdelegate get_by_username(username), to: Beef.Access.Users\n  defdelegate get_by_username_with_follow_info(user_id, username), to: Beef.Access.Users\n  defdelegate search_username(username), to: Beef.Access.Users\n\n  defdelegate get_users_in_current_room(user_id), to: Beef.Access.Users\n  defdelegate tuple_get_current_room_id(user_id), to: Beef.Access.Users\n  defdelegate get_by_id_with_current_room(user_id), to: Beef.Access.Users\n  defdelegate get_current_room(user_id), to: Beef.Access.Users\n  defdelegate get_current_room_id(user_id), to: Beef.Access.Users\n  defdelegate get_ip(user_id), to: Beef.Access.Users\n  defdelegate bot?(user_id), to: Beef.Access.Users\n  defdelegate get_by_api_key(api_key), to: Beef.Access.Users\n  defdelegate count_bot_accounts(user_id), to: Beef.Access.Users\n  # CHOPPING BLOCK\n  ######################################################################################\n\n  # MUTATIONS\n  defdelegate update(changeset), to: Beef.Repo\n\n  defdelegate edit_profile(user_id, data), to: Beef.Mutations.Users\n  defdelegate delete(user_id), to: Beef.Mutations.Users\n  defdelegate bulk_insert(users), to: Beef.Mutations.Users\n  defdelegate inc_num_following(user_id, n), to: Beef.Mutations.Users\n  defdelegate set_reason_for_ban(user_id, reason_for_ban), to: Beef.Mutations.Users\n  defdelegate set_online(user_id), to: Beef.Mutations.Users\n  defdelegate set_user_left_current_room(user_id), to: Beef.Mutations.Users\n  defdelegate set_offline(user_id), to: Beef.Mutations.Users\n\n  defdelegate set_current_room(user_id, room_id), to: Beef.Mutations.Users\n  defdelegate create_bot(user_id, username), to: Beef.Mutations.Users\n\n  # TODO: make can_speak, returning, a single keyword list\n  defdelegate set_current_room(user_id, room_id, can_speak), to: Beef.Mutations.Users\n  defdelegate set_current_room(user_id, room_id, can_speak, returning), to: Beef.Mutations.Users\n  defdelegate twitter_find_or_create(user), to: Beef.Mutations.Users\n  defdelegate set_ip(user_id, ip), to: Beef.Mutations.Users\n  defdelegate github_find_or_create(user, github_access_token), to: Beef.Mutations.Users\n  defdelegate discord_find_or_create(user, discord_access_token), to: Beef.Mutations.Users\nend\n"
  },
  {
    "path": "kousa/lib/broth/legacy_handler.ex",
    "content": "defmodule Broth.LegacyHandler do\n  import Kousa.Utils.Version, only: [sigil_v: 2]\n  import Broth.SocketHandler, only: [prepare_socket_msg: 2]\n  require Logger\n\n  Kousa.MixProject.project()\n  |> Keyword.get(:version)\n  |> Version.parse!()\n  |> Version.compare(~v(0.3.0))\n  |> case do\n    :lt -> :ok\n    _ -> raise CompileError, message: \"this module should not exist beyond version 0.3.0\"\n  end\n\n  def process(%{\"op\" => \"block_user_and_from_room\", \"d\" => payload}, state) do\n    block_user_and_from_room(payload, state)\n  end\n\n  def process(%{\"op\" => \"fetch_follow_list\", \"d\" => payload}, state) do\n    fetch_follow_list(payload, state)\n  end\n\n  def process(%{\"op\" => \"join_room_and_get_info\", \"d\" => payload, \"fetchId\" => fetch_id}, state) do\n    join_room_and_get_info(payload, fetch_id, state)\n  end\n\n  # legacy implementation special cases\n  defp block_user_and_from_room(%{\"userId\" => user_id_to_block}, state) do\n    Logger.error(\n      \"block_user_and_from_room command is deprecated.  Send two user:block and room:ban operations instead\"\n    )\n\n    Kousa.UserBlock.block(state.user.id, user_id_to_block)\n    Kousa.Room.block_from_room(state.user.id, user_id_to_block)\n    nil\n  end\n\n  defp fetch_follow_list(\n         %{\"userId\" => user_id, \"isFollowing\" => get_following_list, \"cursor\" => cursor},\n         state\n       ) do\n    {users, nextCursor} =\n      Kousa.Follow.get_follow_list(state.user.id, user_id, get_following_list, cursor)\n\n    prepare_socket_msg(\n      %{\n        op: \"fetch_follow_list_done\",\n        d: %{\n          isFollowing: get_following_list,\n          userId: user_id,\n          users: users,\n          nextCursor: nextCursor,\n          initial: cursor == 0\n        }\n      },\n      state\n    )\n  end\n\n  defp join_room_and_get_info(%{\"roomId\" => room_id_to_join}, fetch_id, state) do\n    reply =\n      case Kousa.Room.join_room(state.user.id, room_id_to_join) do\n        %{error: err} ->\n          %{op: \"fetch_done\", d: %{error: err}, fetchId: fetch_id}\n\n        %{room: room} ->\n          {room_id, users} = Beef.Users.get_users_in_current_room(state.user.id)\n\n          case Onion.RoomSession.lookup(room_id) do\n            [] ->\n              %{op: \"error\", d: \"Room no longer exists.\"}\n\n            _ ->\n              {muteMap, deafMap, autoSpeaker, activeSpeakerMap} =\n                Onion.RoomSession.get_maps(room_id)\n\n              payload = %{\n                room: room,\n                users: users,\n                muteMap: muteMap,\n                deafMap: deafMap,\n                activeSpeakerMap: activeSpeakerMap,\n                roomId: room_id,\n                autoSpeaker: autoSpeaker,\n                chatMode: Onion.Chat.get(room_id, :chat_mode)\n              }\n\n              %{d: payload, op: \"fetch_done\", fetchId: fetch_id}\n          end\n\n        _ ->\n          %{op: \"error\", d: \"unexpected error\"}\n      end\n\n    prepare_socket_msg(reply, state)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/_types/chat_token.ex",
    "content": "defmodule Broth.Message.Types.ChatToken do\n  use Ecto.Schema\n\n  @message_character_limit Application.compile_env!(:kousa, :message_character_limit)\n\n  @primary_key false\n  embedded_schema do\n    # since this is an embedded schema it's worth being explicit about it.\n    field(:type, Broth.Message.Types.ChatTokenType)\n    field(:value, :string, default: \"\")\n  end\n\n  defimpl Jason.Encoder do\n    def encode(%{type: type, value: value}, opts) do\n      Jason.Encode.map(\n        %{\n          t: type,\n          v: value\n        },\n        opts\n      )\n    end\n  end\n\n  import Ecto.Changeset\n\n  def changeset(changeset, %{\"t\" => type, \"v\" => value}) do\n    changeset(changeset, %{\"type\" => type, \"value\" => value})\n  end\n\n  def changeset(changeset, data) do\n    changeset\n    |> cast(data, [:type, :value])\n    # I removed :value because an empty string for a single token is ok\n    |> validate_required([:type])\n    |> validate_length(:value, min: 0, max: @message_character_limit)\n    |> validate_link\n  end\n\n  defp validate_link(changeset) do\n    if get_change(changeset, :type) == :link do\n      validate_link_uri(changeset)\n    else\n      changeset\n    end\n  end\n\n  @allowed_schemes [\"http\", \"https\"]\n  defp validate_link_uri(changeset) do\n    uri =\n      changeset\n      |> get_change(:value)\n      |> URI.parse()\n\n    if match?(\n         %{host: host, scheme: scheme}\n         when is_binary(host) and scheme in @allowed_schemes,\n         uri\n       ) do\n      changeset\n    else\n      add_error(changeset, :value, \"invalid url\")\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/_types/chat_token_type.ex",
    "content": "import EctoEnum\n\ndefenum(\n  Broth.Message.Types.ChatTokenType,\n  text: 0,\n  mention: 1,\n  block: 2,\n  emote: 3,\n  link: 4,\n  emoji: 5\n)\n"
  },
  {
    "path": "kousa/lib/broth/message/_types/operator.ex",
    "content": "import EctoEnum\n\nalias Broth.Message.User\nalias Broth.Message.Room\nalias Broth.Message.Chat\nalias Broth.Message.Auth\nalias Broth.Message.Misc\n\ndefenum(\n  Broth.Message.Types.Operator,\n  [\n    # user commands and casts: 0..63\n    {User.GetFollowing, 1},\n    {User.GetFollowers, 2},\n    {User.Follow, 3},\n    {User.Ban, 4},\n    {User.Update, 5},\n    {User.GetInfo, 6},\n    {User.GetRelationship, 7},\n    {User.Block, 10},\n    {User.Unfollow, 11},\n    {User.CreateBot, 12},\n    {User.Unblock, 13},\n    {User.GetBots, 14},\n    {User.RevokeApiKey, 15},\n    {User.AdminUpdate, 16},\n    # room commands and casts: 64..127\n    {Room.Invite, 65},\n    {Room.Update, 66},\n    {Room.GetInviteList, 67},\n    {Room.Leave, 68},\n    {Room.Ban, 69},\n    {Room.SetRole, 70},\n    {Room.SetAuth, 71},\n    {Room.Join, 72},\n    {Room.UpdateScheduled, 74},\n    {Room.DeleteScheduled, 75},\n    {Room.Create, 76},\n    {Room.CreateScheduled, 77},\n    {Room.Unban, 78},\n    {Room.GetScheduled, 79},\n    {Room.GetInfo, 80},\n    {Room.GetTop, 81},\n    {Room.SetActiveSpeaker, 82},\n    {Room.Mute, 83},\n    {Room.GetBannedUsers, 84},\n    {Room.Deafen, 85},\n    # chat commands and casts: 128..191\n    {Chat.Ban, 129},\n    {Chat.Send, 130},\n    {Chat.Delete, 131},\n    {Chat.Unban, 132},\n    # auth and maintenance commands 192..254\n    {Auth.Request, 193},\n    {Misc.Search, 210},\n    # etc 255 - 317\n    {BrothTest.MessageTest.TestOperator, 255}\n  ]\n)\n"
  },
  {
    "path": "kousa/lib/broth/message/_types/relationship.ex",
    "content": "import EctoEnum\n\ndefenum(\n  Broth.Message.Types.Relationship,\n  self: 0,\n  following: 1,\n  follower: 2,\n  mutual: 3,\n  nil: 7\n)\n"
  },
  {
    "path": "kousa/lib/broth/message/_types/room_auth.ex",
    "content": "import EctoEnum\n\ndefenum(\n  Broth.Message.Types.RoomAuth,\n  owner: 8,\n  mod: 16,\n  user: 32\n)\n"
  },
  {
    "path": "kousa/lib/broth/message/_types/room_role.ex",
    "content": "import EctoEnum\n\ndefenum(\n  Broth.Message.Types.RoomRole,\n  speaker: 8,\n  raised_hand: 16,\n  listener: 32\n)\n"
  },
  {
    "path": "kousa/lib/broth/message/auth/request.ex",
    "content": "defmodule Broth.Message.Auth.Request do\n  use Broth.Message.Call,\n    needs_auth: false\n\n  @primary_key false\n  embedded_schema do\n    field(:accessToken, :string)\n    field(:refreshToken, :string)\n    field(:platform, :string)\n    field(:currentRoomId, :binary_id)\n    field(:reconnectToVoice, :boolean)\n    field(:muted, :boolean, default: false)\n    field(:deafened, :boolean, default: false)\n  end\n\n  alias Kousa.Utils.UUID\n\n  @impl true\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:accessToken, :refreshToken, :platform, :reconnectToVoice, :muted, :deafened])\n    |> validate_required([:accessToken])\n    |> UUID.normalize(:currentRoomId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: ~w(\n      id\n      username\n      displayName\n      avatarUrl\n      bannerUrl\n      bio\n      online\n      numFollowing\n      numFollowers\n      lastOnline\n    )a}\n\n    @primary_key {:id, :binary_id, []}\n    schema \"users\" do\n      field(:username, :string)\n      field(:displayName, :string)\n      field(:avatarUrl, :string)\n      field(:bannerUrl, :string)\n      field(:bio, :string, default: \"\")\n      field(:currentRoomId, :binary_id)\n      field(:numFollowing, :integer)\n      field(:numFollowers, :integer)\n      field(:online, :boolean)\n      field(:lastOnline, :utc_datetime_usec)\n    end\n  end\n\n  @impl true\n  def execute(changeset, state) do\n    with {:ok, request} <- apply_action(changeset, :validate),\n         {:ok, user} <- Kousa.Auth.authenticate(request, state.ip) do\n      user_ids_i_am_blocking =\n        user\n        |> Beef.Repo.preload(:blocking)\n        |> Map.get(:blocking)\n        |> Enum.map(& &1.id)\n\n      {:reply, user, %{state | user: user, user_ids_i_am_blocking: user_ids_i_am_blocking}}\n    else\n      # don't tolerate malformed requests with any response besides closing\n      # out websocket.\n      _ -> {:close, 4001, \"invalid_authentication\"}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/call.ex",
    "content": "defmodule Broth.Message.Call do\n  @moduledoc \"\"\"\n  API contract statement for call message modules\n\n  ## Usage:\n\n  ```elixir\n  using Broth.Message.Call, reply: <module>\n  ```\n\n  if you omit the reply module, it assumes the value\n  `__MODULE__.Reply`\n\n  performs compile-time checks to validate that the reply module\n  is, indeed, a reply module.\n\n  If this route can be used without authorization, set the\n  `:needs_auth` keyword parameter to false.\n  \"\"\"\n\n  alias Broth.Message.Cast\n\n  defmacro __using__(opts) do\n    default_reply_module = Module.concat(__CALLER__.module, Reply)\n\n    reply_module =\n      opts\n      |> Keyword.get(:reply, default_reply_module)\n      |> Macro.expand(__CALLER__)\n\n    directions =\n      if reply_module == __CALLER__.module do\n        [:inbound, :outbound]\n      else\n        [:inbound]\n      end\n\n    auth_check =\n      opts\n      |> Keyword.get(:needs_auth, true)\n      |> Cast.auth_check()\n\n    quote do\n      use Ecto.Schema\n      import Ecto.Changeset\n\n      @behaviour Broth.Message.Call\n\n      Module.register_attribute(__MODULE__, :directions, accumulate: true, persist: true)\n      @directions unquote(directions)\n\n      unquote(auth_check)\n      unquote(Cast.schema_ast(opts))\n\n      @impl true\n      def reply_module, do: unquote(reply_module)\n\n      @impl true\n      def initialize(_state), do: struct(__MODULE__)\n\n      defoverridable initialize: 1\n\n      # verify compile-time guarantees\n      @after_compile Broth.Message.Call\n    end\n  end\n\n  alias Ecto.Changeset\n  alias Broth.SocketHandler\n\n  @callback auth_check(SocketHandler.state()) :: :ok | {:error, :auth}\n  @callback reply_module() :: module\n  @callback execute(Changeset.t(), SocketHandler.state()) ::\n              {:reply, map, SocketHandler.state()}\n              | {:noreply, SocketHandler.state()}\n              | {:error, map, SocketHandler.state()}\n              | {:error, Changeset.t()}\n              | {:close, code :: 1000..9999, reason :: String.t()}\n\n  @callback initialize(SocketHandler.state()) :: struct\n\n  @callback changeset(struct | nil, Broth.json()) :: Ecto.Changeset.t()\n\n  def __after_compile__(%{module: module}, _bin) do\n    # checks to make sure you've either declared a schema module, or you have\n    # implemented a schema\n    Cast.check_for_schema(module, :inbound)\n\n    # checks to make sure the declared reply module actually exists.\n    reply_module = module.reply_module()\n    Code.ensure_compiled(reply_module)\n\n    Cast.check_for_schema(reply_module, :outbound)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/cast.ex",
    "content": "defmodule Broth.Message.Cast do\n  @moduledoc \"\"\"\n  API contract statement for cast message modules\n\n  If this route can be used without authorization, set the\n  `:needs_auth` keyword parameter to false.\n  \"\"\"\n\n  defmacro __using__(opts) do\n    # needs_auth defaults to true\n    auth_check =\n      opts\n      |> Keyword.get(:needs_auth, true)\n      |> auth_check()\n\n    quote do\n      use Ecto.Schema\n      import Ecto.Changeset\n\n      @behaviour Broth.Message.Cast\n\n      Module.register_attribute(__MODULE__, :directions, accumulate: true, persist: true)\n      @directions [:inbound]\n\n      unquote(auth_check)\n      unquote(schema_ast(opts))\n\n      # default, overrideable intializer value\n\n      @impl true\n      def initialize(_state), do: struct(__MODULE__)\n      defoverridable initialize: 1\n\n      # verify compile-time guarantees\n      @after_compile Broth.Message.Cast\n    end\n  end\n\n  alias Broth.SocketHandler\n  alias Ecto.Changeset\n\n  @callback auth_check(SocketHandler.state()) :: :ok | {:error, :auth}\n\n  @callback changeset(struct | nil, Broth.json()) :: Ecto.Changeset.t()\n\n  @callback initialize(SocketHandler.state()) :: struct()\n\n  @callback execute(Changeset.t(), SocketHandler.state()) ::\n              {:noreply, SocketHandler.state()}\n              | {:error, map, SocketHandler.state()}\n              | {:error, Changeset.t()}\n              | {:exit, code :: 1000..9999, reason :: String.t()}\n\n  def __after_compile__(%{module: module}, _bin) do\n    # checks to make sure you've either declared a schema module, or you have\n    # implemented a schema\n    check_for_schema(module, :inbound)\n  end\n\n  #############################################################################\n  ## generic components shared between call and cast\n\n  def auth_check(true) do\n    quote do\n      @impl true\n      def auth_check(%{user: nil}), do: {:error, :auth}\n      def auth_check(_), do: :ok\n    end\n  end\n\n  def auth_check(false) do\n    quote do\n      @impl true\n      def auth_check(_), do: :ok\n    end\n  end\n\n  def schema_ast(opts) do\n    if schema = opts[:schema] do\n      quote do\n        Module.register_attribute(__MODULE__, :schema, persist: true)\n        @schema unquote(schema)\n      end\n    else\n      quote do\n        Module.register_attribute(__MODULE__, :schema, persist: true)\n        @schema __MODULE__\n      end\n    end\n  end\n\n  def check_for_schema(module, direction) do\n    Code.ensure_compiled(module)\n\n    attributes = module.__info__(:attributes)\n\n    case {attributes[:schema], function_exported?(module, :__schema__, 2)} do\n      {[^module], true} ->\n        :ok\n\n      {[schema_mod], true} ->\n        raise CompileError,\n          description:\n            \"in module #{inspect(module)} you have both declared the schema module #{\n              inspect(schema_mod)\n            } and implemented a schema\"\n\n      {[^module], false} ->\n        raise CompileError,\n          description:\n            \"in module #{inspect(module)} you have failed to declare a schema module or implement a schema\"\n\n      {[schema_mod], false} ->\n        Code.ensure_compiled(schema_mod)\n\n        # commenting out for now because tests started to fail sometimes even at 150ms\n        # Process.sleep(150)\n        # unless function_exported?(schema_mod, :__schema__, 2) do\n        #   raise CompileError,\n        #     description:\n        #       \"in module #{inspect(module)} you declared the schema #{inspect(schema_mod)} but it doesn't appear to have a schema\"\n        # end\n    end\n\n    unless direction in attributes[:directions] do\n      raise CompileError,\n        description: \"the module #{inspect(module)} does not seem to be a #{direction} module\"\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/chat/ban.ex",
    "content": "defmodule Broth.Message.Chat.Ban do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: userId}} <- apply_action(changeset, :validate) do\n      # TODO: change to by: format\n      Kousa.Chat.ban_user(state.user.id, userId)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/chat/delete.ex",
    "content": "defmodule Broth.Message.Chat.Delete do\n  use Broth.Message.Cast\n\n  @derive {Jason.Encoder, only: [:messageId, :userId, :deleterId]}\n  @primary_key false\n  embedded_schema do\n    field(:messageId, :binary_id)\n\n    # NB: the userId is the owner of the message.  This may not necessarily be\n    # the PID of the websocket, since a mod or owner of a room should be able\n    # to delete other people's messages.  It's the responsibility of the FE\n    # to make sure both the messageId and the userId match up correctly, or\n    # else deletion authority could be spoofed.\n\n    field(:userId, :binary_id)\n    field(:deleterId, :binary_id)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def initialize(state) do\n    %__MODULE__{deleterId: state.user.id}\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:messageId, :userId])\n    |> validate_required([:messageId, :userId])\n    |> UUID.normalize(:messageId)\n    |> UUID.normalize(:userId)\n  end\n\n  def execute(changeset, state) do\n    with {:ok, deletion} <- apply_action(changeset, :validate) do\n      Kousa.Chat.delete_msg(deletion, by: state.user.id)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/chat/send.ex",
    "content": "defmodule Broth.Message.Chat.Send do\n  use Broth.Message.Cast\n\n  alias Broth.Message.Types.ChatToken\n\n  @message_character_limit Application.compile_env!(:kousa, :message_character_limit)\n\n  @derive {Jason.Encoder, only: [:id, :tokens, :from, :sentAt, :isWhisper]}\n\n  @primary_key false\n  embedded_schema do\n    field(:id, :binary_id)\n    embeds_many(:tokens, ChatToken)\n    field(:whisperedTo, {:array, :binary_id})\n    field(:from, :binary_id)\n    field(:sentAt, :utc_datetime)\n    field(:isWhisper, :boolean, default: false)\n  end\n\n  @impl true\n  def initialize(state) do\n    %__MODULE__{\n      id: UUID.uuid4(),\n      from: state.user.id,\n      sentAt: DateTime.utc_now()\n    }\n  end\n\n  @impl true\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:whisperedTo])\n    |> default_whispered_to\n    |> put_tokens(data[\"tokens\"])\n    |> validate_whispered_to\n  end\n\n  defp default_whispered_to(changeset) do\n    if get_field(changeset, :whisperedTo) do\n      changeset\n    else\n      put_change(changeset, :whisperedTo, [])\n    end\n  end\n\n  def put_tokens(changeset, []) do\n    add_error(changeset, :tokens, \"must not be empty\")\n  end\n\n  def put_tokens(changeset, tokens) when is_list(tokens) do\n    {embedded_tokens, acc_length} =\n      Enum.map_reduce(tokens, 0, &apply_changeset_accumulate_length/2)\n\n    if acc_length == 0 do\n      throw(tokens: {\"no empty messages\", []})\n    end\n\n    put_embed(changeset, :tokens, embedded_tokens)\n  rescue\n    _ in Ecto.CastError ->\n      add_error(changeset, :tokens, \"is invalid\")\n  catch\n    errors ->\n      %{changeset | errors: errors ++ changeset.errors, valid?: false}\n  end\n\n  def put_tokens(changeset, _invalid_tokens) do\n    add_error(changeset, :tokens, \"is invalid\")\n  end\n\n  defp apply_changeset_accumulate_length(token, length) do\n    changeset = ChatToken.changeset(%ChatToken{}, token)\n    new_length = length + text_size(changeset)\n\n    case {changeset.valid?, new_length <= @message_character_limit} do\n      {false, _} ->\n        throw(changeset.errors)\n\n      {true, false} ->\n        throw(tokens: {\"combined length too long\", []})\n\n      {true, true} ->\n        :ok\n    end\n\n    {changeset, new_length}\n  end\n\n  # this fn crashes if :value is nil\n  # but :value should be defaulting to \"\" now\n  defp text_size(changeset) do\n    changeset\n    |> get_field(:value)\n    |> :erlang.byte_size()\n  end\n\n  alias Kousa.Utils.UUID\n\n  def validate_whispered_to(changeset) do\n    if whisper_target = get_field(changeset, :whisperedTo) do\n      normalized_uuids =\n        Enum.map(whisper_target, fn target ->\n          case UUID.normalize(target) do\n            {:ok, nil} ->\n              throw(:format_error)\n\n            {:ok, normalized} ->\n              normalized\n\n            _ ->\n              throw(:format_error)\n          end\n        end)\n\n      changeset\n      |> put_change(:whisperedTo, normalized_uuids)\n      |> put_change(:isWhisper, normalized_uuids != [])\n    else\n      changeset\n    end\n  catch\n    :format_error -> add_error(changeset, :whisperedTo, \"is invalid\")\n  end\n\n  @impl true\n  def execute(changeset, state) do\n    with {:ok, payload} <- apply_action(changeset, :validate) do\n      # note that payload bears the user_id inside of its `from` parameter.\n      Kousa.Chat.send_msg(payload)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/chat/unban.ex",
    "content": "defmodule Broth.Message.Chat.Unban do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: userId}} <- apply_action(changeset, :validate) do\n      # TODO: change to by: format\n      Kousa.Chat.unban_user(state.user.id, userId)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/manifest.ex",
    "content": "defmodule Broth.Message.Manifest do\n  alias Broth.Message.Auth\n  alias Broth.Message.Chat\n  alias Broth.Message.Room\n  alias Broth.Message.User\n  alias Broth.Message.Misc\n\n  @actions %{\n    \"test:operator\" => BrothTest.MessageTest.TestOperator,\n    \"user:create_bot\" => User.CreateBot,\n    \"user:ban\" => User.Ban,\n    \"user:block\" => User.Block,\n    \"user:unblock\" => User.Unblock,\n    \"user:follow\" => User.Follow,\n    \"user:get_following\" => User.GetFollowing,\n    \"user:get_followers\" => User.GetFollowers,\n    \"user:update\" => User.Update,\n    \"user:get_info\" => User.GetInfo,\n    \"user:get_bots\" => User.GetBots,\n    \"user:revoke_api_key\" => User.RevokeApiKey,\n    \"user:get_relationship\" => User.GetRelationship,\n    \"user:unfollow\" => User.Unfollow,\n    \"user:admin_update\" => User.AdminUpdate,\n    \"room:invite\" => Room.Invite,\n    \"room:update\" => Room.Update,\n    \"room:get_invite_list\" => Room.GetInviteList,\n    \"room:leave\" => Room.Leave,\n    \"room:ban\" => Room.Ban,\n    \"room:set_role\" => Room.SetRole,\n    \"room:set_auth\" => Room.SetAuth,\n    \"room:join\" => Room.Join,\n    \"room:get_banned_users\" => Room.GetBannedUsers,\n    \"room:update_scheduled\" => Room.UpdateScheduled,\n    \"room:delete_scheduled\" => Room.DeleteScheduled,\n    \"room:create\" => Room.Create,\n    \"room:create_scheduled\" => Room.CreateScheduled,\n    \"room:unban\" => Room.Unban,\n    \"room:get_info\" => Room.GetInfo,\n    \"room:get_top\" => Room.GetTop,\n    \"room:set_active_speaker\" => Room.SetActiveSpeaker,\n    \"room:mute\" => Room.Mute,\n    \"room:deafen\" => Room.Deafen,\n    \"room:get_scheduled\" => Room.GetScheduled,\n    \"chat:ban\" => Chat.Ban,\n    \"chat:unban\" => Chat.Unban,\n    \"chat:send_msg\" => Chat.Send,\n    \"chat:delete\" => Chat.Delete,\n    \"auth:request\" => Auth.Request,\n    \"misc:search\" => Misc.Search\n  }\n\n  # verify that all of the actions are accounted for in the\n  # operators list\n  alias Broth.Message.Types.Operator\n  require Operator\n\n  @actions\n  |> Map.values()\n  |> Enum.each(fn module ->\n    Operator.valid_value?(module) ||\n      raise CompileError,\n        description: \"the module #{inspect(module)} is not a member of #{inspect(Operator)}\"\n  end)\n\n  def actions, do: @actions\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/misc/search.ex",
    "content": "defmodule Broth.Message.Misc.Search do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:query, :string)\n    # not used currently, but will be used in the future:\n    field(:cursor, :integer)\n    field(:limit, :integer)\n  end\n\n  @impl true\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:query])\n    |> validate_required([:query])\n    |> validate_length(:query, min: 3, max: 100)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: ~w(\n        items\n        rooms\n        users\n        nextCursor\n      )a}\n\n    @primary_key false\n    embedded_schema do\n      # the types of this is Room | User.\n      # currently not enforced, but once we have real DisplayRoom and\n      # DisplayUser schemas we'll make sure Search.search outputs those.\n      field(:items, {:array, :map})\n      embeds_many(:rooms, Beef.Schemas.Room)\n      embeds_many(:users, Beef.Schemas.User)\n      field(:nextCursor, :integer)\n    end\n  end\n\n  alias Beef.Users\n  alias Beef.Rooms\n\n  @impl true\n  def execute(changeset, state) do\n    case apply_action(changeset, :validate) do\n      {:ok, %{query: query}} ->\n        rooms = Rooms.search_name(query)\n        users = Users.search_username(query)\n        items = Enum.concat(rooms, users)\n\n        {:reply, %Reply{items: items, rooms: rooms, users: users, nextCursor: nil}, state}\n\n      error ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/push.ex",
    "content": "defmodule Broth.Message.Push do\n  @moduledoc \"\"\"\n  API contract statement for push message modules\n  \"\"\"\n\n  alias Broth.Message.Cast\n\n  defmacro __using__(opts) do\n    quote do\n      use Ecto.Schema\n      import Ecto.Changeset\n\n      @behaviour Broth.Message.Push\n\n      Module.register_attribute(__MODULE__, :directions, accumulate: true, persist: true)\n      @directions [:outbound]\n\n      unquote(Cast.schema_ast(opts))\n\n      @after_compile Broth.Message.Push\n    end\n  end\n\n  @callback changeset(Broth.json()) :: Ecto.Changeset.t()\n  @callback operation() :: String.t()\n\n  @optional_callbacks [changeset: 1, operation: 0]\n\n  def __after_compile__(%{module: module}, _bin) do\n    # checks to make sure you've either declared a schema module, or you have\n    # implemented a schema\n    Cast.check_for_schema(module, :outbound)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/ban.ex",
    "content": "defmodule Broth.Message.Room.Ban do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n    field(:shouldBanIp, :boolean, default: false)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId, :shouldBanIp])\n    |> validate_required([:userId, :shouldBanIp])\n    |> UUID.normalize(:userId)\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: user_id, shouldBanIp: should_ban_ip}} <-\n           apply_action(changeset, :validate) do\n      # TODO: change to auth: format.\n      Kousa.Room.block_from_room(state.user.id, user_id, should_ban_ip)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/create.ex",
    "content": "defmodule Broth.Message.Room.Create do\n  use Broth.Message.Call,\n    reply: __MODULE__\n\n  @derive {Jason.Encoder,\n           only: [\n             :id,\n             :creatorId,\n             :name,\n             :description,\n             :isPrivate,\n             :scheduledRoomId\n           ]}\n\n  @primary_key {:id, :binary_id, []}\n  schema \"rooms\" do\n    field(:creatorId, :binary_id)\n    field(:name, :string)\n    field(:description, :string)\n    field(:isPrivate, :boolean, default: false)\n    field(:userIdToInvite, {:array, :binary_id}, virtual: true)\n    field(:autoSpeaker, :boolean)\n    field(:scheduledRoomId, :binary_id, virtual: true)\n  end\n\n  # inbound data.\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [\n      :name,\n      :description,\n      :isPrivate,\n      :userIdToInvite,\n      :autoSpeaker,\n      :scheduledRoomId\n    ])\n    |> validate_required([:name])\n  end\n\n  alias Beef.ScheduledRooms\n\n  def execute(changeset!, state) do\n    changeset! = put_change(changeset!, :creatorId, state.user.id)\n\n    # TODO: pass the changeset to the create_room and avoid the validation\n    # step.\n    with {:ok, room_spec} <- apply_action(changeset!, :validation),\n         {:ok, %{room: room}} <-\n           Kousa.Room.create_room(\n             state.user.id,\n             room_spec.name,\n             room_spec.description || \"\",\n             room_spec.isPrivate,\n             room_spec.userIdToInvite,\n             room_spec.autoSpeaker\n           ) do\n      case Ecto.UUID.cast(room_spec.scheduledRoomId) do\n        {:ok, _} ->\n          ScheduledRooms.room_started(state.user.id, room_spec.scheduledRoomId, room.id)\n\n        _ ->\n          nil\n      end\n\n      {:reply, struct(__MODULE__, Map.from_struct(room)), state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/create_scheduled.ex",
    "content": "defmodule Broth.Message.Room.CreateScheduled do\n  use Broth.Message.Call,\n    reply: __MODULE__\n\n  @derive {Jason.Encoder, only: [:id, :name, :scheduledFor, :description]}\n\n  @primary_key {:id, :binary_id, []}\n  schema \"scheduled_room\" do\n    field(:name, :string)\n    field(:scheduledFor, :utc_datetime_usec)\n    field(:description, :string, default: \"\")\n\n    belongs_to(:creator, User, foreign_key: :creatorId, type: :binary_id)\n  end\n\n  @impl true\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:name, :scheduledFor, :description])\n    |> validate_required([:name, :scheduledFor])\n    |> validate_future\n  end\n\n  def validate_future(changeset = %{valid?: false}), do: changeset\n\n  def validate_future(changeset) do\n    changeset\n    |> get_field(:scheduledFor)\n    |> DateTime.compare(DateTime.utc_now())\n    |> case do\n      :lt -> add_error(changeset, :scheduledFor, \"is in the past\")\n      _ -> changeset\n    end\n  end\n\n  @impl true\n  # TODO: this will be much cleaner when direct changeset posting is enabled.\n  def execute(changeset, state) do\n    with {:ok, room} <- apply_action(changeset, :validation),\n         room_data = %{\n           \"name\" => room.name,\n           \"scheduledFor\" => room.scheduledFor,\n           \"description\" => room.description\n         },\n         {:ok, s_room} <- Kousa.ScheduledRoom.schedule(state.user.id, room_data) do\n      reply = %__MODULE__{\n        id: s_room.id,\n        name: s_room.name,\n        scheduledFor: s_room.scheduledFor,\n        description: s_room.description\n      }\n\n      {:reply, reply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/deafen.ex",
    "content": "defmodule Broth.Message.Room.Deafen do\n  use Broth.Message.Call\n  @primary_key false\n  embedded_schema do\n    field(:deafened, :boolean)\n  end\n\n  # inbound data.\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:deafened])\n    |> validate_required([:deafened])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n    @derive {Jason.Encoder, only: []}\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{deafened: deafened?}} <- apply_action(changeset, :validation) do\n      Onion.UserSession.set_deafen(state.user.id, deafened?)\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/delete_scheduled.ex",
    "content": "defmodule Broth.Message.Room.DeleteScheduled do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:roomId, :binary_id)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:roomId])\n    |> validate_required([:roomId])\n    |> UUID.normalize(:roomId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{roomId: room_id}} <- apply_action(changeset, :validate) do\n      Kousa.ScheduledRoom.delete(state.user.id, room_id)\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/get_banned_users.ex",
    "content": "defmodule Broth.Message.Room.GetBannedUsers do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:cursor, :integer, default: 0)\n    field(:limit, :integer, default: 100)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:cursor, :limit])\n    |> validate_number(:limit, greater_than: 0, message: \"too low\")\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @primary_key false\n\n    @derive {Jason.Encoder, only: [:users, :nextCursor]}\n\n    embedded_schema do\n      embeds_many(:users, Beef.Schemas.User)\n      field(:nextCursor, :integer)\n    end\n  end\n\n  alias Kousa.RoomBlock\n\n  def execute(changeset, state) do\n    with {:ok, request} <- apply_action(changeset, :validate) do\n      case RoomBlock.get_blocked_users(state.user.id, request.cursor) do\n        {users, next_cursor} ->\n          {:reply, %Reply{users: users, nextCursor: next_cursor}, state}\n\n        _ ->\n          {:reply, %Reply{users: [], nextCursor: nil}, state}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/get_info.ex",
    "content": "defmodule Broth.Message.Room.GetInfo do\n  use Broth.Message.Call\n  alias Beef.Repo\n\n  @primary_key false\n  embedded_schema do\n    # not required.  If you don't supply it, you get the room id of the\n    # current room you're in (if you are in one)\n    field(:roomId, :binary_id)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:roomId])\n    |> UUID.normalize(:roomId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:id, :name, :description, :isPrivate]}\n\n    @primary_key {:id, :binary_id, []}\n    schema \"rooms\" do\n      field(:name, :string)\n      field(:description, :string)\n      field(:isPrivate, :boolean)\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, request} <- apply_action(changeset, :validate) do\n      room_id = request.roomId || Beef.Users.get_current_room_id(state.user.id)\n\n      if room = room_id && Repo.get(Reply, room_id) do\n        {:reply, room, state}\n      else\n        {:error, \"the room doesn't exist\"}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/get_invite_list.ex",
    "content": "defmodule Broth.Message.Room.GetInviteList do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:cursor, :integer, default: 0)\n    field(:limit, :integer, default: 100)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:cursor, :limit])\n    |> validate_number(:limit, greater_than: 0, message: \"too low\")\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:invites, :nextCursor]}\n\n    @primary_key false\n    embedded_schema do\n      embeds_many(:invites, Beef.Schemas.User)\n      field(:nextCursor, :integer)\n      field(:initial, :boolean)\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, request} <- apply_action(changeset, :validate),\n         {users, nextCursor} <- Beef.Follows.fetch_invite_list(state.user.id, request.cursor) do\n      {:reply, %Reply{invites: users, nextCursor: nextCursor, initial: request.cursor == 0},\n       state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/get_scheduled.ex",
    "content": "defmodule Broth.Message.Room.GetScheduled do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    # TO BE CHANGED TO UTC_DATETIME, WHERE A DT IN THE PAST IS HISTORICAL\n    # A DT IN THE FUTURE IS LIMIT, and nil is all\n    field(:range, :string, default: \"all\")\n    field(:userId, :binary_id)\n    field(:cursor, :integer)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:range, :userId, :cursor])\n    |> validate_inclusion(:range, [\"all\", \"upcoming\"])\n\n    # to be made explicit in the future, currently we need \"user\"\n    # hack to get this to work.\n    #    |> UUID.normalize(:userId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:rooms, :nextCursor]}\n\n    embedded_schema do\n      # TO BE CHANGED TO DISPLAYROOM.\n      field(:rooms, {:array, :map})\n      field(:nextCursor, :integer)\n    end\n  end\n\n  def execute(changeset, state = %{user: %{id: user_id}}) do\n    with {:ok, request} <- apply_action(changeset, :validate) do\n      case request do\n        %{userId: nil, range: \"all\"} ->\n          {rooms, next_cursor} =\n            Kousa.ScheduledRoom.get_scheduled_rooms(\n              user_id,\n              false,\n              request.cursor\n            )\n\n          {:reply, %Reply{rooms: rooms, nextCursor: next_cursor}, state}\n\n        %{userId: u, range: \"all\"} when u == \"self\" or u == user_id ->\n          {rooms, next_cursor} =\n            Kousa.ScheduledRoom.get_scheduled_rooms(\n              user_id,\n              true,\n              request.cursor\n            )\n\n          {:reply, %Reply{rooms: rooms, nextCursor: next_cursor}, state}\n\n        %{userId: u, range: \"all\"} when u != \"self\" ->\n          {rooms, next_cursor} =\n            Kousa.ScheduledRoom.get_scheduled_rooms(\n              u,\n              true,\n              request.cursor\n            )\n\n          {:reply, %Reply{rooms: rooms, nextCursor: next_cursor}, state}\n\n        %{userId: u, range: \"upcoming\"} when u == \"self\" or u == user_id ->\n          rooms = Kousa.ScheduledRoom.get_my_scheduled_rooms_about_to_start(user_id)\n          {:reply, %Reply{rooms: rooms}, state}\n\n        _ ->\n          {:error, \"not supported yet\"}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/get_top.ex",
    "content": "defmodule Broth.Message.Room.GetTop do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:cursor, :integer, default: 0)\n    field(:limit, :integer, default: 100)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:cursor, :limit])\n    |> validate_number(:limit, greater_than: 0, message: \"too low\")\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:rooms, :nextCursor, :initial]}\n\n    @primary_key false\n    embedded_schema do\n      embeds_many(:rooms, Beef.Schemas.Room)\n      field(:nextCursor, :integer)\n      field(:initial, :boolean)\n    end\n  end\n\n  alias Beef.Rooms\n\n  def execute(changeset, state) do\n    with {:ok, request} <- apply_action(changeset, :validate),\n         {rooms, nextCursor} <- Rooms.get_top_public_rooms(state.user.id, request.cursor) do\n      {:reply, %Reply{rooms: rooms, nextCursor: nextCursor, initial: request.cursor == 0}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/invite.ex",
    "content": "defmodule Broth.Message.Room.Invite do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n  end\n\n  def execute(data, state) do\n    case apply_action(data, :validate) do\n      {:ok, invite} ->\n        Kousa.Room.invite_to_room(state.user.id, invite.userId)\n        {:noreply, state}\n\n      error = {:error, _} ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/join.ex",
    "content": "defmodule Broth.Message.Room.Join do\n  use Broth.Message.Call\n  alias Beef.Repo\n\n  @primary_key false\n  embedded_schema do\n    field(:roomId, :binary_id)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:roomId])\n    |> validate_required([:roomId])\n    |> UUID.normalize(:roomId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:id, :name, :description, :isPrivate]}\n\n    @primary_key {:id, :binary_id, []}\n    schema \"rooms\" do\n      field(:name, :string)\n      field(:description, :string)\n      field(:isPrivate, :boolean)\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{roomId: room_id}} <- apply_action(changeset, :validate) do\n      case Kousa.Room.join_room(state.user.id, room_id) do\n        %{error: error} ->\n          {:error, error, state}\n\n        _ ->\n          {:reply, Repo.get(Reply, room_id), state}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/leave.ex",
    "content": "defmodule Broth.Message.Room.Leave do\n  @moduledoc \"\"\"\n  Note that this is different from `Broth.Message.Room.Update`, because this\n  is a user-driven boolean value that translates into a MapSet in the parent\n  struct; it is also a cast and not a call.\n\n  Moreover, the security parameters for this are different from the security\n  parameters of Update call.\n  \"\"\"\n\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    change(initializer, data)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(_, state) do\n    case Kousa.Room.leave_room(state.user.id) do\n      {:ok, _} ->\n        {:reply, %Reply{}, state}\n\n      _ ->\n        {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/mute.ex",
    "content": "defmodule Broth.Message.Room.Mute do\n  use Broth.Message.Call\n  @primary_key false\n  embedded_schema do\n    field(:muted, :boolean)\n  end\n\n  # inbound data.\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:muted])\n    |> validate_required([:muted])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n    @derive {Jason.Encoder, only: []}\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{muted: muted?}} <- apply_action(changeset, :validation) do\n      Onion.UserSession.set_mute(state.user.id, muted?)\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/set_active_speaker.ex",
    "content": "defmodule Broth.Message.Room.SetActiveSpeaker do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:active, :boolean)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:active])\n    |> validate_required([:active])\n  end\n\n  def execute(changeset, state = %{user: %{id: user_id}}) do\n    with {:ok, %{active: active?}} <- apply_action(changeset, :validate),\n         room_id when not is_nil(room_id) <- Beef.Users.get_current_room_id(user_id) do\n      Onion.RoomSession.speaking_change(room_id, user_id, active?)\n      {:noreply, state}\n    else\n      nil -> {:error, \"not in a room\"}\n      error -> error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/set_auth.ex",
    "content": "defmodule Broth.Message.Room.SetAuth do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n    field(:level, Broth.Message.Types.RoomAuth)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId, :level])\n    |> validate_required([:userId, :level])\n    |> UUID.normalize(:userId)\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: user_id, level: level}} <- apply_action(changeset, :validate) do\n      Kousa.Room.set_auth(user_id, level, by: state.user.id)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/set_role.ex",
    "content": "defmodule Broth.Message.Room.SetRole do\n  use Broth.Message.Cast\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n    field(:role, Broth.Message.Types.RoomRole)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def initialize(state) do\n    # TODO: obtain the initial state of this first prior to changing it.\n    %__MODULE__{userId: state.user.id}\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId, :role])\n    # if we don't have an id, assume self.\n    |> validate_required([:userId, :role])\n    |> UUID.normalize(:id)\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: user_id, role: role}} <- apply_action(changeset, :validate) do\n      Kousa.Room.set_role(user_id, role, by: state.user.id)\n      {:noreply, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/unban.ex",
    "content": "defmodule Broth.Message.Room.Unban do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n    |> UUID.normalize(:userId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: user_id}} <- apply_action(changeset, :validate) do\n      Kousa.RoomBlock.unban(state.user.id, user_id)\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/update.ex",
    "content": "defmodule Broth.Message.Room.Update do\n  alias Beef.Schemas.Room\n\n  use Broth.Message.Call,\n    schema: Room,\n    reply: Room\n\n  @impl true\n  def initialize(state) do\n    Beef.Rooms.get_room_by_creator_id(state.user.id)\n  end\n\n  @impl true\n  def changeset(nil, _) do\n    %Ecto.Changeset{}\n    # generally 404 on an auth error\n    |> add_error(:id, \"does not exist\")\n  end\n\n  @impl true\n  def changeset(initializer, data) do\n    initializer\n    |> cast(data, ~w(description isPrivate name autoSpeaker chatMode chatThrottle)a)\n    |> validate_required([:name])\n    |> validate_number(:chatThrottle, greater_than_or_equal_to: 0)\n  end\n\n  @impl true\n  def execute(changeset, state) do\n    # TODO: move this changeset stuff into Kousa itself.\n    with {:ok, update} <- apply_action(changeset, :validate),\n         {:ok, room} <- Kousa.Room.update(state.user.id, Map.from_struct(update)) do\n      changes = changeset.changes\n\n      if Map.has_key?(changes, :isPrivate) do\n        # send the room_privacy_change message.\n        Onion.RoomSession.broadcast_ws(\n          room.id,\n          %{\n            op: \"room_privacy_change\",\n            d: %{roomId: room.id, name: room.name, isPrivate: changes.isPrivate}\n          }\n        )\n      end\n\n      if Map.has_key?(changes, :chatThrottle) do\n        # send the room_privacy_change message.\n        Onion.RoomSession.broadcast_ws(\n          room.id,\n          %{\n            op: \"room_chat_throttle_change\",\n            d: %{roomId: room.id, name: room.name, chatThrottle: changes.chatThrottle}\n          }\n        )\n\n        Onion.Chat.set(\n          room.id,\n          :chat_throttle,\n          changes.chatThrottle\n        )\n      end\n\n      if Map.has_key?(changes, :autoSpeaker) do\n        Onion.RoomSession.set_auto_speaker(\n          room.id,\n          changes.autoSpeaker\n        )\n      end\n\n      if Map.has_key?(changes, :chatMode) do\n        # send the room_privacy_change message.\n        Onion.Chat.set(\n          room.id,\n          :chat_mode,\n          changes.chatMode\n        )\n\n        Onion.RoomSession.broadcast_ws(\n          room.id,\n          %{\n            op: \"room_chat_mode_changed\",\n            d: %{roomId: room.id, chatMode: changes.chatMode}\n          }\n        )\n      end\n\n      {:reply, room, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/room/update_scheduled.ex",
    "content": "defmodule Broth.Message.Room.UpdateScheduled do\n  use Broth.Message.Call,\n    reply: __MODULE__\n\n  alias Beef.Repo\n\n  @derive {Jason.Encoder, only: [:name, :scheduledFor, :description]}\n  @primary_key {:id, :binary_id, []}\n  schema \"scheduled_rooms\" do\n    field(:name, :string)\n    field(:scheduledFor, :utc_datetime_usec)\n    field(:description, :string, default: \"\")\n  end\n\n  import Broth.Message.Room.CreateScheduled, only: [validate_future: 1]\n\n  def changeset(initializer \\\\ %__MODULE__{}, data)\n\n  def changeset(_, data)\n      when not is_map_key(data, \"id\") or\n             is_nil(:erlang.map_get(\"id\", data)) do\n    id_error(\"can't be blank\")\n  end\n\n  def changeset(_, data) do\n    case Repo.get(__MODULE__, data[\"id\"]) do\n      nil ->\n        id_error(\"room not found\")\n\n      room ->\n        room\n        |> cast(data, [:name, :scheduledFor, :description])\n        |> validate_required([:name, :scheduledFor])\n        |> validate_future\n    end\n  end\n\n  def id_error(message) do\n    %__MODULE__{}\n    |> change\n    |> add_error(:id, message)\n  end\n\n  def execute(changeset, state) do\n    with {:ok, update} <- apply_action(changeset, :validate),\n         update_data = update |> Map.from_struct() |> Map.delete(:id),\n         :ok <- Kousa.ScheduledRoom.edit(state.user.id, update.id, update_data) do\n      {:reply, Repo.get(__MODULE__, update.id), state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/admin_update.ex",
    "content": "defmodule Broth.Message.User.AdminUpdate do\n  alias Beef.Schemas.User\n\n  use Broth.Message.Call,\n    reply: User\n\n  @primary_key false\n  embedded_schema do\n    field(:id, :binary_id)\n    embeds_one(:user, User)\n  end\n\n  alias Beef.Users\n\n  def user_admin_changeset(_, data, user) do\n    user\n    |> cast(data, [:staff, :contributions])\n  end\n\n  @impl true\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:id])\n    |> validate_required([:id])\n    |> cast_embed(:user,\n      with:\n        {__MODULE__, :user_admin_changeset,\n         [\n           if(not is_nil(data[\"id\"]),\n             do: Users.get_by_id(data[\"id\"]),\n             else: %User{}\n           )\n         ]},\n      required: true\n    )\n  end\n\n  alias Broth.SocketHandler\n\n  @impl true\n  def execute(changeset, %SocketHandler{} = state) do\n    with %Ecto.Changeset{changes: %{user: user_changeset}} <- changeset do\n      # @todo we are changing the values for another user\n      # we need to update the cached data in that users ws\n      case Kousa.User.admin_update_with(\n             %{user_changeset | action: :update},\n             state.user\n           ) do\n        {:ok, user} -> {:reply, user, state}\n        error -> error\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/ban.ex",
    "content": "defmodule Broth.Message.User.Ban do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary)\n    field(:reason, :string)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId, :reason])\n    |> validate_required([:userId, :reason])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, request} <- apply_action(changeset, :validate),\n         :ok <- Kousa.User.ban(request.userId, request.reason, admin_id: state.user.id) do\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/block.ex",
    "content": "defmodule Broth.Message.User.Block do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n  end\n\n  defmodule Reply do\n    # TODO: make the reply be a schema that returns the entire user\n    # database object\n\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:blocked, :error]}\n\n    @primary_key false\n    embedded_schema do\n      field(:blocked, {:array, :binary_id})\n      # field is nil when there is no error.\n      field(:error, :string)\n    end\n  end\n\n  alias Broth.SocketHandler\n\n  def execute(changeset, %SocketHandler{} = state) do\n    with {:ok, %{userId: user_id}} <- apply_action(changeset, :validate),\n         {:ok, %{userIdBlocked: blocked}} <- Kousa.UserBlock.block(state.user.id, user_id) do\n      # TODO: update this to return a full user update.\n      {:reply, %Reply{blocked: [blocked]},\n       %{state | user_ids_i_am_blocking: [user_id | state.user_ids_i_am_blocking]}}\n    else\n      error = {:error, %Ecto.Changeset{}} ->\n        error\n\n      {:error, _error} ->\n        user_id = get_field(changeset, :userId)\n        {:reply, %Reply{error: \"error blocking #{user_id}\"}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/create_bot.ex",
    "content": "defmodule Broth.Message.User.CreateBot do\n  use Broth.Message.Call,\n    reply: __MODULE__\n\n  @derive {Jason.Encoder, only: [:username]}\n\n  @primary_key {:id, :binary_id, []}\n  schema \"users\" do\n    field(:username, :string)\n  end\n\n  # inbound data.\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:username])\n    |> validate_required([:username])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: ~w(\n        apiKey\n        isUsernameTaken\n        error\n      )a}\n\n    @primary_key false\n    embedded_schema do\n      field(:apiKey, :string)\n      field(:isUsernameTaken, :boolean)\n      # @todo conver to proper error handling\n      field(:error, :string)\n    end\n  end\n\n  alias Beef.Users\n  alias Beef.Schemas.User\n\n  def execute(changeset!, state) do\n    with {:ok, %{username: username}} <- apply_action(changeset!, :validation) do\n      cond do\n        Users.bot?(state.user.id) ->\n          {:reply, %Reply{error: \"bots can't create bots\"}, state}\n\n        Users.count_bot_accounts(state.user.id) > 4 ->\n          {:reply, %Reply{error: \"you've reached the max of 5 bot accounts\"}, state}\n\n        true ->\n          case Users.create_bot(state.user.id, username) do\n            {:ok, %User{apiKey: apiKey}} ->\n              {:reply, %Reply{apiKey: apiKey}, state}\n\n            {:error,\n             %Ecto.Changeset{\n               errors: [username: {\"has already been taken\", _}]\n             }} ->\n              {:reply, %Reply{isUsernameTaken: true}, state}\n          end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/follow.ex",
    "content": "defmodule Broth.Message.User.Follow do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: user_id}} <- apply_action(changeset, :validate) do\n      Kousa.Follow.follow(state.user.id, user_id, true)\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/get_bots.ex",
    "content": "defmodule Broth.Message.User.GetBots do\n  import Ecto.Query, warn: false\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n  end\n\n  # userId is either a uuid or username\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: ~w(\n      id\n      username\n      displayName\n      avatarUrl\n      apiKey\n    )a}\n\n    @primary_key {:id, :binary_id, []}\n    schema \"users\" do\n      field(:username, :string)\n      field(:displayName, :string)\n      field(:avatarUrl, :string)\n      field(:apiKey, :binary_id)\n\n      belongs_to(:botOwner, Beef.Schemas.User, foreign_key: :botOwnerId, type: :binary_id)\n    end\n  end\n\n  alias Beef.Repo\n  alias Broth.SocketHandler\n\n  def execute(changeset, %SocketHandler{} = state) do\n    case apply_action(changeset, :validate) do\n      {:ok, _} ->\n        {:reply, %{bots: Repo.all(from(r in Reply, where: r.botOwnerId == ^state.user.id))},\n         state}\n\n      error ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/get_followers.ex",
    "content": "defmodule Broth.Message.User.GetFollowers do\n  use Broth.Message.Call\n\n  alias Kousa.Utils.UUID\n\n  @primary_key false\n  embedded_schema do\n    # TODO: add a userId key in here.\n    field(:username, :string)\n    field(:cursor, :integer, default: 0)\n    field(:limit, :integer, default: 100)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:cursor, :limit, :username])\n    |> validate_number(:limit, greater_than: 0, message: \"too low\")\n    |> UUID.normalize(:userId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:followers, :nextCursor, :initial]}\n    @primary_key false\n    embedded_schema do\n      embeds_many(:followers, Beef.Schemas.User)\n      field(:nextCursor, :integer)\n      field(:initial, :boolean)\n    end\n  end\n\n  def execute(changeset, state) do\n    # currently limit is unused.\n    with {:ok, %{username: username, cursor: cursor, limit: _limit}} <-\n           apply_action(changeset, :validate) do\n      {users, next_cursor} =\n        if is_nil(username) do\n          Kousa.Follow.get_follow_list(state.user.id, state.user.id, false, cursor)\n        else\n          Kousa.Follow.get_follow_list_by_username(state.user.id, username, false, cursor)\n        end\n\n      {:reply, %Reply{followers: users, nextCursor: next_cursor, initial: cursor == 0}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/get_following.ex",
    "content": "defmodule Broth.Message.User.GetFollowing do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:username, :string)\n    field(:cursor, :integer, default: 0)\n    field(:limit, :integer, default: 100)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:cursor, :limit, :username])\n    |> validate_number(:limit, greater_than: 0, message: \"too low\")\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:following, :nextCursor]}\n\n    @primary_key false\n    embedded_schema do\n      embeds_many(:following, Beef.Schemas.User)\n      field(:nextCursor, :integer)\n      field(:initial, :boolean)\n    end\n  end\n\n  def execute(changeset, state) do\n    alias Beef.Follows\n\n    with {:ok, request} <- apply_action(changeset, :validate) do\n      {users, next_cursor} =\n        if is_nil(request.username) do\n          Follows.get_my_following(state.user.id, request.cursor)\n        else\n          Kousa.Follow.get_follow_list_by_username(\n            state.user.id,\n            request.username,\n            true,\n            request.cursor\n          )\n        end\n\n      {:reply,\n       %Reply{\n         following: users,\n         nextCursor: next_cursor,\n         initial: request.cursor == 0\n       }, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/get_info.ex",
    "content": "defmodule Broth.Message.User.GetInfo do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    # required.\n    field(:userIdOrUsername, :string)\n  end\n\n  # userId is either a uuid or username\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userIdOrUsername])\n    |> validate_required([:userIdOrUsername])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: ~w(\n      id\n      username\n      displayName\n      avatarUrl\n      bannerUrl\n      bio\n      online\n      numFollowing\n      numFollowers\n      lastOnline\n      iBlockedThem\n    )a}\n\n    @primary_key {:id, :binary_id, []}\n    schema \"users\" do\n      field(:username, :string)\n      field(:displayName, :string)\n      field(:avatarUrl, :string)\n      field(:bannerUrl, :string)\n      field(:bio, :string, default: \"\")\n      field(:currentRoomId, :binary_id)\n      field(:numFollowing, :integer)\n      field(:numFollowers, :integer)\n      field(:online, :boolean)\n      field(:lastOnline, :utc_datetime_usec)\n      field(:youAreFollowing, :boolean, virtual: true)\n      field(:followsYou, :boolean, virtual: true)\n      field(:iBlockedThem, :boolean, virtual: true)\n      field(:error, :string, virtual: true)\n    end\n  end\n\n  alias Beef.Users\n\n  def execute(changeset, state) do\n    case apply_action(changeset, :validate) do\n      {:ok, %{userIdOrUsername: userIdOrUsername}} ->\n        user =\n          case Ecto.UUID.cast(userIdOrUsername) do\n            {:ok, _} ->\n              Users.get_by_id_with_follow_info(state.user.id, userIdOrUsername)\n\n            _ ->\n              Users.get_by_username_with_follow_info(state.user.id, userIdOrUsername)\n          end\n\n        case user do\n          nil ->\n            {:reply, %{error: \"could not find user\"}, state}\n\n          %{theyBlockedMe: true} ->\n            {:reply, %{error: \"blocked\"}, state}\n\n          _ ->\n            {:reply, user, state}\n        end\n\n      error ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/get_relationship.ex",
    "content": "defmodule Broth.Message.User.GetRelationship do\n  # TO BE DEPRECATED IN FAVOR OF SOMETHING ELSE.  ONCE PRELOADS HAVE BEEN\n  # FULLY OPTIMIZED, IT SHOULD BE POSSIBLE TO GLEAN THIS INFORMATION OFF\n  # OF PRELOAD INFORMATION.\n\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    # required.\n    field(:userId, :binary_id)\n  end\n\n  alias Beef.Follows\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n    |> UUID.normalize(:userId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: [:relationship]}\n\n    @primary_key false\n    embedded_schema do\n      field(:relationship, Broth.Message.Types.Relationship)\n    end\n  end\n\n  def execute(changeset, state = %{user: %{id: user_id}}) do\n    case apply_action(changeset, :validate) do\n      {:ok, %{userId: ^user_id}} ->\n        {:reply, %Reply{relationship: :self}, state}\n\n      {:ok, %{userId: user_id}} ->\n        r =\n          case Follows.get_info(state.user.id, user_id) do\n            %{followsYou: false, youAreFollowing: false} -> nil\n            %{followsYou: true, youAreFollowing: false} -> :follower\n            %{followsYou: false, youAreFollowing: true} -> :following\n            %{followsYou: true, youAreFollowing: true} -> :mutual\n          end\n\n        {:reply, %Reply{relationship: r}, state}\n\n      error = {:error, _} ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/revoke_api_key.ex",
    "content": "defmodule Broth.Message.User.RevokeApiKey do\n  import Ecto.Query, warn: false\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n    |> Kousa.Utils.UUID.normalize(:userId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: ~w(\n      id\n      apiKey\n    )a}\n\n    @primary_key {:id, :binary_id, []}\n    schema \"users\" do\n      field(:apiKey, :string)\n    end\n  end\n\n  alias Broth.SocketHandler\n\n  def execute(changeset, %SocketHandler{} = state) do\n    case apply_action(changeset, :validate) do\n      {:ok, %{userId: bot_id}} ->\n        case Kousa.User.revoke_api_key(state.user.id, bot_id) do\n          {:ok, new_api_key} ->\n            {:reply, %Reply{id: bot_id, apiKey: new_api_key}, state}\n\n          error ->\n            error\n        end\n\n      error ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/unblock.ex",
    "content": "defmodule Broth.Message.User.Unblock do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  alias Kousa.Utils.UUID\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n    |> UUID.normalize(:userId)\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  alias Beef.UserBlocks\n  alias Broth.SocketHandler\n\n  def execute(changeset, %SocketHandler{} = state) do\n    with {:ok, %{userId: user_id}} <- apply_action(changeset, :validate) do\n      UserBlocks.delete(state.user.id, user_id)\n\n      {:reply, %Reply{},\n       %{\n         state\n         | user_ids_i_am_blocking: Enum.filter(state.user_ids_i_am_blocking, &(&1 != user_id))\n       }}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/unfollow.ex",
    "content": "defmodule Broth.Message.User.Unfollow do\n  use Broth.Message.Call\n\n  @primary_key false\n  embedded_schema do\n    field(:userId, :binary_id)\n  end\n\n  def changeset(initializer \\\\ %__MODULE__{}, data) do\n    initializer\n    |> cast(data, [:userId])\n    |> validate_required([:userId])\n  end\n\n  defmodule Reply do\n    use Broth.Message.Push\n\n    @derive {Jason.Encoder, only: []}\n\n    @primary_key false\n    embedded_schema do\n    end\n  end\n\n  def execute(changeset, state) do\n    with {:ok, %{userId: user_id}} <- apply_action(changeset, :validate) do\n      Kousa.Follow.follow(state.user.id, user_id, false)\n      {:reply, %Reply{}, state}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message/user/update.ex",
    "content": "defmodule Broth.Message.User.Update do\n  alias Beef.Schemas.User\n\n  use Broth.Message.Call,\n    schema: User,\n    reply: User\n\n  @impl true\n  def initialize(state) do\n    state.user\n  end\n\n  @impl true\n  def changeset(initializer \\\\ %User{}, data) do\n    initializer\n    |> cast(data, [\n      :muted,\n      :deafened,\n      :username,\n      :bio,\n      :displayName,\n      :whisperPrivacySetting\n    ])\n    |> validate_required([:username])\n    |> update_change(:displayName, &String.trim/1)\n    |> validate_length(:bio, min: 0, max: 160)\n    |> validate_length(:displayName, min: 2, max: 50)\n    |> validate_format(:username, ~r/^[\\w\\.]{4,15}$/)\n    |> validate_format(\n      :avatarUrl,\n      ~r/^https?:\\/\\/(www\\.|)((a|p)bs.twimg.com\\/(profile_images|sticky\\/default_profile_images)\\/(.*)\\.(jpg|png|jpeg|webp)|avatars\\.githubusercontent\\.com\\/u\\/[^\\s]+|github.com\\/identicons\\/[^\\s]+|cdn.discordapp.com\\/avatars\\/[^\\s]+\\/[^\\s]+\\.(jpg|png|jpeg|webp))/\n    )\n    |> validate_format(\n      :bannerUrl,\n      ~r/^https?:\\/\\/(www\\.|)(pbs.twimg.com\\/profile_banners\\/(.+)\\/(.+)(?:\\.(jpg|png|jpeg|webp))?|avatars\\.githubusercontent\\.com\\/u\\/)/\n    )\n    |> unique_constraint(:username)\n  end\n\n  @impl true\n  def execute(changeset, state) do\n    # TODO: make this a proper changeset-mediated alteration.\n    case Kousa.User.update_with(changeset) do\n      {:ok, user} -> {:reply, user, %{state | user: user}}\n      error -> error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/message.ex",
    "content": "defmodule Broth.Message do\n  use Ecto.Schema\n\n  alias Ecto.Changeset\n  import Changeset\n\n  @primary_key false\n  embedded_schema do\n    field(:operator, Broth.Message.Types.Operator, null: false)\n    field(:payload, :map)\n    field(:reference, :binary_id)\n    field(:inbound_operator, :string)\n    field(:version, Kousa.Utils.Version)\n    # reply messages only\n    field(:errors, :map)\n  end\n\n  @type t :: %__MODULE__{\n          operator: module(),\n          payload: map(),\n          reference: Kousa.Utils.UUID.t(),\n          inbound_operator: String.t()\n        }\n\n  @spec changeset(%{String.t() => Broth.json()}, Broth.SocketHandler.state()) :: Changeset.t()\n  @doc \"\"\"\n  Primary validation function for all websocket messages.\n  \"\"\"\n  def changeset(data, state) do\n    %__MODULE__{}\n    |> cast(data, [:inbound_operator])\n    |> Map.put(:params, data)\n    |> find(:operator)\n    |> find(:payload)\n    |> find(:reference, :optional)\n    |> cast_operator\n    |> cast_reference\n    |> cast_inbound_operator\n    |> cast_payload(state)\n    |> validate_calls_have_references\n    |> find(:version)\n    |> cast_version\n  end\n\n  @type message_field :: :operator | :payload | :reference\n\n  #########################################################################\n  # TODO: deprecate other forms, convert into them in \"Translator\".\n  # then collapse find into some simpler methods.\n\n  @valid_forms %{\n    operator: ~w(operator op),\n    payload: ~w(payload p d),\n    reference: ~w(reference ref fetchId),\n    version: ~w(version v)\n  }\n\n  defp find(changeset, field, optional \\\\ false)\n  defp find(changeset = %{valid?: false}, _, _), do: changeset\n\n  defp find(changeset, field, optional) when is_atom(field) do\n    find(changeset, field, @valid_forms[field], optional)\n  end\n\n  @spec find(Changeset.t(), message_field, [String.t()], :optional | false) :: Changeset.t()\n\n  defp find(changeset = %{params: params}, field, [form | _], _)\n       when is_map_key(params, form) do\n    %{changeset | params: Map.put(changeset.params, \"#{field}\", params[form])}\n  end\n\n  defp find(changeset, field, [_ | rest], optional), do: find(changeset, field, rest, optional)\n\n  defp find(changeset, field, [], optional) do\n    if optional do\n      changeset\n    else\n      add_error(changeset, field, \"no #{field} present\")\n    end\n  end\n\n  ############################################################################\n\n  @operators Broth.Message.Manifest.actions()\n\n  defp cast_operator(changeset = %{valid?: false}), do: changeset\n\n  defp cast_operator(changeset = %{params: %{\"operator\" => op}}) do\n    if operator = @operators[op] do\n      changeset\n      |> put_change(:operator, operator)\n      |> put_change(:inbound_operator, op)\n    else\n      add_error(changeset, :operator, \"#{op} is invalid\")\n    end\n  end\n\n  defp cast_reference(changeset = %{valid?: false}), do: changeset\n\n  defp cast_reference(changeset = %{params: %{\"reference\" => reference}}) do\n    put_change(changeset, :reference, reference)\n  end\n\n  defp cast_reference(changeset), do: changeset\n\n  defp cast_inbound_operator(changeset) do\n    if get_field(changeset, :inbound_operator) do\n      changeset\n    else\n      inbound_operator = get_field(changeset, :operator)\n      put_change(changeset, :inbound_operator, inbound_operator)\n    end\n  end\n\n  defp cast_payload(changeset = %{valid?: false}, _), do: changeset\n\n  defp cast_payload(changeset, state) do\n    operator = get_field(changeset, :operator)\n\n    state\n    |> operator.initialize()\n    |> operator.changeset(changeset.params[\"payload\"])\n    |> case do\n      inner_changeset = %{valid?: true} ->\n        put_change(changeset, :payload, inner_changeset)\n\n      inner_changeset = %{valid?: false} ->\n        errors = Kousa.Utils.Errors.changeset_errors(inner_changeset)\n        put_change(changeset, :errors, errors)\n    end\n  end\n\n  defp cast_version(changeset = %{valid?: false}), do: changeset\n\n  defp cast_version(changeset = %{params: params}) do\n    if Map.has_key?(params, \"version\") do\n      cast(changeset, params, [:version])\n    else\n      add_error(changeset, :version, \"is required\")\n    end\n  end\n\n  defp validate_calls_have_references(changeset = %{valid?: false}), do: changeset\n\n  defp validate_calls_have_references(changeset) do\n    operator = get_field(changeset, :operator)\n\n    # if the operator has a reply submodule then it must be a \"call\" message.\n    # verify that these\n    if function_exported?(operator, :reply_module, 0) do\n      validate_required(changeset, [:reference], message: \"is required for #{inspect(operator)}\")\n    else\n      changeset\n    end\n  end\n\n  # encoding will only happen on egress out to the websocket.\n  defimpl Jason.Encoder do\n    def encode(message, opts) do\n      %{\n        op: operator(message),\n        p: message.payload,\n        v: message.version\n      }\n      |> add_reference(message)\n      |> add_errors(message)\n      |> Broth.Translator.translate_outbound(message)\n      |> Jason.Encode.map(opts)\n    end\n\n    defp operator(%{operator: op}) when is_binary(op), do: op\n\n    defp operator(%{operator: op}) when is_atom(op) do\n      if function_exported?(op, :operator, 0) do\n        op.operator()\n      end\n    end\n\n    defp add_reference(map, %{reference: nil}), do: map\n    defp add_reference(map, %{reference: ref}), do: Map.put(map, :ref, ref)\n\n    defp add_errors(map, %{errors: nil}), do: map\n    defp add_errors(map, %{errors: e}), do: Map.put(map, :e, e)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/plugs/check_auth.ex",
    "content": "defmodule Broth.Plugs.CheckAuth do\n  import Plug.Conn\n\n  def init(opts) do\n    opts\n  end\n\n  def call(conn = %{req_headers: req_headers}, opts) do\n    access_token = :proplists.get_value(\"x-access-token\", req_headers, nil)\n    refresh_token = :proplists.get_value(\"x-refresh-token\", req_headers, nil)\n\n    if access_token && refresh_token do\n      assign_tokens(conn, access_token, refresh_token, opts)\n    else\n      auth_error(conn, opts)\n    end\n  end\n\n  defp assign_tokens(conn, access_token, refresh_token, opts) do\n    case Kousa.Utils.TokenUtils.tokens_to_user_id(access_token, refresh_token) do\n      nil ->\n        auth_error(conn, opts)\n\n      {:existing_claim, user_id} ->\n        assign(conn, :user_id, user_id)\n\n      {:new_tokens, user_id, %{accessToken: new_access_token, refreshToken: new_refresh_token},\n       user} ->\n        conn\n        |> put_resp_header(\n          \"X-Access-Token\",\n          new_access_token\n        )\n        |> put_resp_header(\n          \"X-Refresh-Token\",\n          new_refresh_token\n        )\n        |> assign(:user_id, user_id)\n        |> assign(:user, user)\n    end\n  end\n\n  defp auth_error(conn, opts) do\n    if opts[:shouldThrow] do\n      conn\n      |> send_resp(400, \"Not authenticated\")\n      |> halt\n    else\n      conn\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/plugs/cors.ex",
    "content": "defmodule Broth.Plugs.Cors do\n  import Plug.Conn\n\n  def init(_) do\n  end\n\n  def call(conn, _opts) do\n    conn\n    |> put_resp_header(\"Access-Control-Allow-Origin\", \"*\")\n    |> put_resp_header(\"Access-Control-Allow-Method\", \"POST, GET, OPTIONS\")\n    |> put_resp_header(\"Access-Control-Max-Age\", \"86400\")\n    |> put_resp_header(\n      \"Access-Control-Allow-Headers\",\n      \"Origin, X-Access-Token, X-Refresh-Token, Content-Type, Accept\"\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/plugs/redirect.ex",
    "content": "defmodule Broth.Plugs.Redirect do\n  import Plug.Conn\n\n  def redirect(conn, url) do\n    html = Plug.HTML.html_escape(url)\n    body = \"<html><body>You are being <a href=\\\"#{html}\\\">redirected</a>.</body></html>\"\n\n    conn\n    |> put_resp_header(\"location\", url)\n    |> put_resp_content_type(\"text/html\")\n    |> send_resp(302, body)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/bot_auth.ex",
    "content": "defmodule Broth.Routes.BotAuth do\n  import Plug.Conn\n\n  use Plug.Router\n\n  plug(Broth.Plugs.Cors)\n\n  plug(Plug.Parsers,\n    parsers: [:json],\n    pass: [\"text/*\"],\n    json_decoder: Jason\n  )\n\n  plug(:match)\n  plug(:dispatch)\n\n  alias Onion.BotAuthRateLimit\n  alias Beef.Users\n\n  @env Mix.env()\n\n  post \"/auth\" do\n    with %{\"apiKey\" => api_key} <- conn.body_params,\n         {:ok, _} <- Ecto.UUID.cast(api_key) do\n      is_test = :test == @env\n\n      key =\n        with true <- is_test,\n             x when not is_nil(x) <- :proplists.get_value(\"rate-limit-key\", conn.req_headers, nil) do\n          x\n        else\n          _ -> IP.to_string(conn.remote_ip)\n        end\n\n      max_attempts = if is_test, do: 5, else: 20\n\n      if (BotAuthRateLimit.get(key) || 0) > max_attempts do\n        conn\n        |> put_resp_content_type(\"application/json\")\n        |> send_resp(\n          429,\n          Jason.encode!(%{error: \"too many invalid requests\"})\n        )\n      else\n        user = Users.get_by_api_key(api_key)\n\n        cond do\n          is_nil(user) ->\n            # @todo refactor to atomic increment\n            # this didn't work :(\n            # BotAuthRateLimit.update_counter(key, 1, 1)\n            BotAuthRateLimit.set(key, (BotAuthRateLimit.get(key) || 0) + 1)\n\n            conn\n            |> put_resp_content_type(\"application/json\")\n            |> send_resp(\n              400,\n              Jason.encode!(%{error: \"invalid input\"})\n            )\n\n          not is_nil(user.reasonForBan) ->\n            conn\n            |> put_resp_content_type(\"application/json\")\n            |> send_resp(\n              400,\n              Jason.encode!(%{error: \"your account is banned\"})\n            )\n\n          true ->\n            conn\n            |> put_resp_content_type(\"application/json\")\n            |> send_resp(\n              200,\n              Jason.encode!(%{\n                username: user.username,\n                accessToken: Kousa.AccessToken.generate_and_sign!(%{\"userId\" => user.id}),\n                refreshToken:\n                  Kousa.RefreshToken.generate_and_sign!(%{\n                    \"userId\" => user.id,\n                    \"tokenVersion\" => user.tokenVersion\n                  })\n              })\n            )\n        end\n      end\n    else\n      _ ->\n        conn\n        |> put_resp_content_type(\"application/json\")\n        |> send_resp(\n          400,\n          Jason.encode!(%{error: \"invalid input\"})\n        )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/dev_only.ex",
    "content": "defmodule Broth.Routes.DevOnly do\n  import Plug.Conn\n\n  alias Beef.Schemas.User\n\n  use Plug.Router\n\n  plug(:match)\n  plug(:dispatch)\n\n  get \"/test-info\" do\n    env = Application.fetch_env!(:kousa, :env)\n    staging? = Application.get_env(:kousa, :staging?)\n\n    if env == :dev || staging? do\n      username = fetch_query_params(conn).query_params[\"username\"]\n      user = Beef.Users.get_by_username(username)\n\n      conn\n      |> put_resp_content_type(\"application/json\")\n      |> send_resp(\n        200,\n        Jason.encode!(\n          Kousa.Utils.TokenUtils.create_tokens(\n            if(is_nil(user),\n              do:\n                Beef.Repo.insert!(\n                  %User{\n                    username: username,\n                    email: \"test@\" <> username <> \"test.com\",\n                    githubAccessToken: \"\",\n                    githubId: \"id:\" <> username,\n                    avatarUrl: \"https://placekitten.com/200/200\",\n                    bannerUrl: \"https://placekitten.com/1000/300\",\n                    displayName: String.capitalize(username),\n                    bio:\n                      \"This is some interesting info about the ex-founder of nothing, welcome to the bio of such a ocol pers on !\"\n                  },\n                  returning: true\n                ),\n              else: user\n            )\n          )\n        )\n      )\n    else\n      conn\n      |> put_resp_content_type(\"application/json\")\n      |> send_resp(400, Jason.encode!(%{\"error\" => \"no\"}))\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/discord_auth.ex",
    "content": "defmodule Broth.Routes.DiscordAuth do\n  import Plug.Conn\n  use Plug.Router\n\n  alias Beef.Users\n\n  plug(:match)\n  plug(:dispatch)\n\n  get \"/web\" do\n    state =\n      if Application.get_env(:kousa, :staging?) do\n        %{\n          redirect_base_url: fetch_query_params(conn).query_params[\"redirect_after_base\"]\n        }\n        |> Jason.encode!()\n        |> Base.encode64()\n      else\n        \"web\"\n      end\n\n    %{conn | params: Map.put(conn.params, \"state\", state)}\n    |> Plug.Conn.put_private(:ueberauth_request_options, %{\n      callback_url: Application.get_env(:kousa, :api_url) <> \"/auth/discord/callback\",\n      options: [\n        default_scope: \"identify email\",\n        prompt: \"none\" \n      ]\n    })\n    |> Ueberauth.Strategy.Discord.handle_request!()\n  end\n\n  get \"/callback\" do\n    conn\n    |> fetch_query_params()\n    |> Plug.Conn.put_private(:ueberauth_request_options, %{\n      callback_url: Application.get_env(:kousa, :api_url) <> \"/auth/discord/callback\",\n      options: []\n    })\n    |> Ueberauth.Strategy.Discord.handle_callback!()\n    |> handle_callback()\n  end\n\n  def get_base_url(conn) do\n    with true <- Application.get_env(:kousa, :staging?),\n         state <- Map.get(conn.query_params, \"state\", \"\"),\n         {:ok, json} <- Base.decode64(state),\n         {:ok, %{\"redirect_base_url\" => redirect_base_url}} when is_binary(redirect_base_url) <-\n           Jason.decode(json) do\n      redirect_base_url\n    else\n      _ ->\n        Application.fetch_env!(:kousa, :web_url)\n    end\n  end\n\n  def handle_callback(\n        %Plug.Conn{assigns: %{ueberauth_failure: %{errors: [%{message_key: \"missing_code\"}]}}} =\n          conn\n      ) do\n    conn\n    |> Broth.Plugs.Redirect.redirect(\n      get_base_url(conn) <>\n        \"/?error=\" <>\n        URI.encode(\"try again\")\n    )\n  end\n\n  def handle_callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn) do\n    IO.puts(\"Discord oauth failure\")\n    IO.inspect(failure)\n\n    conn\n    |> Broth.Plugs.Redirect.redirect(\n      get_base_url(conn) <>\n        \"/?error=\" <>\n        URI.encode(\n          \"something went wrong, try again and if the error persists, tell ben to check the server logs\"\n        )\n    )\n  end\n\n  def handle_callback(\n        %Plug.Conn{private: %{discord_user: user, discord_token: %{access_token: access_token}}} =\n          conn\n      ) do\n    try do\n      {_, db_user} = Users.discord_find_or_create(user, access_token)\n\n      if not is_nil(db_user.reasonForBan) do\n        conn\n        |> Broth.Plugs.Redirect.redirect(\n          get_base_url(conn) <>\n            \"/?error=\" <>\n            URI.encode(\n              \"your account got banned, if you think this was a mistake, please send me an email at benawadapps@gmail.com\"\n            )\n        )\n      else\n        conn\n        |> Broth.Plugs.Redirect.redirect(\n          get_base_url(conn) <>\n            \"/?accessToken=\" <>\n            Kousa.AccessToken.generate_and_sign!(%{\"userId\" => db_user.id}) <>\n            \"&refreshToken=\" <>\n            Kousa.RefreshToken.generate_and_sign!(%{\n              \"userId\" => db_user.id,\n              \"tokenVersion\" => db_user.tokenVersion\n            })\n        )\n      end\n    rescue\n      e in RuntimeError ->\n        conn\n        |> Broth.Plugs.Redirect.redirect(\n          get_base_url(conn) <>\n            \"/?error=\" <>\n            URI.encode(e.message)\n        )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/github_auth.ex",
    "content": "defmodule Broth.Routes.GitHubAuth do\n  import Plug.Conn\n  use Plug.Router\n\n  require Logger\n  alias Beef.Users\n  alias Kousa.Utils.Urls\n\n  plug(:match)\n  plug(:dispatch)\n\n  get \"/web\" do\n    redirect_to_next =\n      Enum.any?(conn.req_headers, fn {k, v} ->\n        k == \"referer\" and Urls.next_site_url?(v)\n      end)\n\n    state =\n      %{\n        redirect_base_url:\n          if(Application.get_env(:kousa, :staging?),\n            do: fetch_query_params(conn).query_params[\"redirect_after_base\"],\n            else: \"web\"\n          ),\n        redirect_to_next: redirect_to_next\n      }\n      |> Jason.encode!()\n      |> Base.encode64()\n\n    %{conn | params: Map.put(conn.params, \"state\", state)}\n    |> Plug.Conn.put_private(:ueberauth_request_options, %{\n      callback_url: Application.get_env(:kousa, :api_url) <> \"/auth/github/callback\",\n      options: [\n        default_scope: \"read:user,user:email\"\n      ]\n    })\n    |> Ueberauth.Strategy.Github.handle_request!()\n  end\n\n  get \"/callback\" do\n    conn\n    |> fetch_query_params()\n    |> Plug.Conn.put_private(:ueberauth_request_options, %{\n      options: []\n    })\n    |> Ueberauth.Strategy.Github.handle_callback!()\n    |> handle_callback()\n  end\n\n  @spec get_base_url(Plug.Conn.t()) :: String.t()\n  def get_base_url(conn) do\n    with state <- Map.get(conn.query_params, \"state\", \"\"),\n         {:ok, json} <- Base.decode64(state),\n         {:ok,\n          %{\"redirect_base_url\" => redirect_base_url, \"redirect_to_next\" => redirect_to_next}}\n         when is_binary(redirect_base_url) <-\n           Jason.decode(json) do\n      cond do\n        redirect_to_next ->\n          \"https://next.dogehouse.tv\"\n\n        Application.get_env(:kousa, :staging?) ->\n          redirect_base_url\n\n        true ->\n          Application.fetch_env!(:kousa, :web_url)\n      end\n    else\n      _ ->\n        Application.fetch_env!(:kousa, :web_url)\n    end\n  end\n\n  def handle_callback(\n        %Plug.Conn{assigns: %{ueberauth_failure: %{errors: [%{message_key: \"missing_code\"}]}}} =\n          conn\n      ) do\n    conn\n    |> Broth.Plugs.Redirect.redirect(\n      get_base_url(conn) <>\n        \"/?error=\" <>\n        URI.encode(\"try again\")\n    )\n  end\n\n  def handle_callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn) do\n    Logger.warn(\"github oauth failure: #{inspect(failure)}\")\n\n    conn\n    |> Broth.Plugs.Redirect.redirect(\n      get_base_url(conn) <>\n        \"/?error=\" <>\n        URI.encode(\n          \"something went wrong, try again and if the error persists, tell ben to check the server logs\"\n        )\n    )\n  end\n\n  def handle_callback(\n        %Plug.Conn{private: %{github_user: user, github_token: %{access_token: access_token}}} =\n          conn\n      ) do\n    try do\n      {_, db_user} =\n        Users.github_find_or_create(\n          %{user | \"email\" => Kousa.Github.pick_primary_email(user[\"emails\"])},\n          access_token\n        )\n\n      if not is_nil(db_user.reasonForBan) do\n        conn\n        |> Broth.Plugs.Redirect.redirect(\n          get_base_url(conn) <>\n            \"/?error=\" <>\n            URI.encode(\n              \"your account got banned, if you think this was a mistake, please send me an email at benawadapps@gmail.com\"\n            )\n        )\n      else\n        conn\n        |> Broth.Plugs.Redirect.redirect(\n          get_base_url(conn) <>\n            \"/?accessToken=\" <>\n            Kousa.AccessToken.generate_and_sign!(%{\"userId\" => db_user.id}) <>\n            \"&refreshToken=\" <>\n            Kousa.RefreshToken.generate_and_sign!(%{\n              \"userId\" => db_user.id,\n              \"tokenVersion\" => db_user.tokenVersion\n            })\n        )\n      end\n    rescue\n      e in RuntimeError ->\n        conn\n        |> Broth.Plugs.Redirect.redirect(\n          get_base_url(conn) <>\n            \"/?error=\" <>\n            URI.encode(e.message)\n        )\n    end\n  end\n\n  def handle_callback(conn) do\n    Logger.warn(\"unhandled handle_callback #{inspect(conn)}\")\n\n    conn\n    |> Broth.Plugs.Redirect.redirect(\n      get_base_url(conn) <>\n        \"/?error=\" <>\n        URI.encode(\n          \"something went wrong, try again and if the error persists, tell ben to check the server logs\"\n        )\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/room.ex",
    "content": "defmodule Broth.Routes.Room do\n  import Plug.Conn\n\n  alias Beef.Rooms\n  alias Ecto.UUID\n\n  use Plug.Router\n\n  plug(Broth.Plugs.Cors)\n  plug(:match)\n  plug(:dispatch)\n\n  get \"/:id\" do\n    %Plug.Conn{params: %{\"id\" => id}} = conn\n\n    case UUID.cast(id) do\n      {:ok, uuid} ->\n        room = Rooms.get_room_by_id(uuid)\n\n        cond do\n          is_nil(room) ->\n            conn\n            |> put_resp_content_type(\"application/json\")\n            |> send_resp(\n              400,\n              Jason.encode!(%{error: \"room does not exist\"})\n            )\n\n          room.isPrivate ->\n            conn\n            |> put_resp_content_type(\"application/json\")\n            |> send_resp(\n              400,\n              Jason.encode!(%{error: \"room is not public\"})\n            )\n\n          true ->\n            conn\n            |> put_resp_content_type(\"application/json\")\n            |> send_resp(\n              200,\n              Jason.encode!(%{room: room})\n            )\n        end\n\n      _ ->\n        conn\n        |> put_resp_content_type(\"application/json\")\n        |> send_resp(\n          400,\n          Jason.encode!(%{error: \"invalid id\"})\n        )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/scheduled_room.ex",
    "content": "defmodule Broth.Routes.ScheduledRoom do\n  import Plug.Conn\n\n  alias Beef.ScheduledRooms\n  alias Ecto.UUID\n\n  use Plug.Router\n\n  plug(Broth.Plugs.Cors)\n  plug(:match)\n  plug(:dispatch)\n\n  get \"/:id\" do\n    %Plug.Conn{params: %{\"id\" => id}} = conn\n\n    case UUID.cast(id) do\n      {:ok, uuid} ->\n        conn\n        |> put_resp_content_type(\"application/json\")\n        |> send_resp(\n          200,\n          Jason.encode!(%{room: ScheduledRooms.get_by_id(uuid)})\n        )\n\n      _ ->\n        conn\n        |> put_resp_content_type(\"application/json\")\n        |> send_resp(\n          200,\n          Jason.encode!(%{error: \"invalid id\"})\n        )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/stats.ex",
    "content": "defmodule Broth.Routes.Stats do\n  import Plug.Conn\n  import Ecto.Query\n\n  use Plug.Router\n  use Timex\n\n  plug(Broth.Plugs.Cors)\n  plug(:match)\n  plug(:dispatch)\n\n  alias Onion.StatsCache\n  alias Beef.Repo\n  alias Beef.Schemas.User\n\n  defp getStats do\n    two_days_ago = Timex.now() |> Timex.shift(days: -2)\n\n    query =\n      from(u in User,\n        where: u.lastOnline > ^two_days_ago or u.online\n      )\n\n    numActive = Repo.aggregate(query, :count, :id)\n\n    d = {DateTime.now!(\"Etc/UTC\"), {Repo.aggregate(User, :count, :id), numActive}}\n    StatsCache.set(\"main\", d)\n    d\n  end\n\n  get \"/\" do\n    {dt, {numUsers, numActive}} =\n      case StatsCache.get(\"main\") do\n        nil ->\n          getStats()\n\n        {dt, stats} ->\n          # 1 day\n          exp_dt = DateTime.add(dt, 60 * 60 * 24, :second)\n\n          if :lt == DateTime.compare(exp_dt, DateTime.now!(\"Etc/UTC\")) do\n            getStats()\n          else\n            {dt, stats}\n          end\n      end\n\n    conn\n    |> put_resp_content_type(\"application/json\")\n    |> send_resp(\n      200,\n      Jason.encode!(%{\n        \"numUsers\" => numUsers,\n        \"activeInLastTwoDays\" => numActive,\n        \"lastUpdated\" => DateTime.to_iso8601(dt)\n      })\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/twitter_auth.ex",
    "content": "defmodule Broth.Routes.TwitterAuth do\n  import Plug.Conn\n  use Plug.Router\n\n  require Logger\n  alias Beef.Users\n  alias Kousa.Utils.Urls\n  alias Broth.Plugs.Redirect\n\n  plug(:put_secret_key_base)\n\n  plug(Plug.Session,\n    store: :cookie,\n    key: \"_dogehouse_session\",\n    signing_salt: \"YaQoOWg5\"\n  )\n\n  plug(:match)\n  plug(:dispatch)\n\n  def put_secret_key_base(conn, _) do\n    put_in(conn.secret_key_base, Application.get_env(:kousa, :secret_key_base))\n  end\n\n  get \"/web\" do\n    token =\n      ExTwitter.request_token(\n        Application.get_env(:kousa, :api_url) <>\n          \"/auth/twitter/callback\"\n      )\n\n    {:ok, authenticate_url} = ExTwitter.authenticate_url(token.oauth_token)\n\n    conn\n    |> fetch_session()\n    |> put_session(\n      :redirect_to_next,\n      Enum.any?(conn.req_headers, fn {k, v} ->\n        k == \"referer\" and Urls.next_site_url?(v)\n      end)\n    )\n    |> Redirect.redirect(authenticate_url)\n  end\n\n  get \"/callback\" do\n    conn_with_qp =\n      conn\n      |> fetch_query_params\n      |> fetch_session()\n\n    base_url = get_base_url(conn_with_qp)\n\n    try do\n      with %{\"oauth_token\" => oauth_token, \"oauth_verifier\" => oauth_verifier} <-\n             conn_with_qp.query_params,\n           {:ok, access_token} <- ExTwitter.access_token(oauth_verifier, oauth_token),\n           _ <-\n             ExTwitter.configure(\n               consumer_key: System.get_env(\"TWITTER_API_KEY\"),\n               consumer_secret: System.get_env(\"TWITTER_SECRET_KEY\"),\n               access_token: access_token.oauth_token,\n               access_token_secret: access_token.oauth_token_secret\n             ),\n           %ExTwitter.Model.User{\n             description: bio,\n             name: displayName,\n             id_str: twitterId,\n             raw_data: %{email: email},\n             profile_image_url_https: avatarUrl,\n             profile_banner_url: bannerUrl\n           } <- ExTwitter.verify_credentials(include_email: true),\n           {_, db_user} <-\n             Users.twitter_find_or_create(%{\n               bio: bio,\n               displayName: displayName,\n               twitterId: twitterId,\n               bannerUrl: bannerUrl,\n               email: email,\n               avatarUrl: avatarUrl\n             }) do\n        if not is_nil(db_user.reasonForBan) do\n          conn\n          |> Redirect.redirect(\n            base_url <>\n              \"/?error=\" <>\n              URI.encode(\n                \"your account got banned, if you think this was a mistake, please send me an email at benawadapps@gmail.com\"\n              )\n          )\n        else\n          conn\n          |> Redirect.redirect(\n            base_url <>\n              \"/?accessToken=\" <>\n              Kousa.AccessToken.generate_and_sign!(%{\"userId\" => db_user.id}) <>\n              \"&refreshToken=\" <>\n              Kousa.RefreshToken.generate_and_sign!(%{\n                \"userId\" => db_user.id,\n                \"tokenVersion\" => db_user.tokenVersion\n              })\n          )\n        end\n      else\n        x ->\n          IO.inspect(x)\n\n          conn\n          |> Redirect.redirect(\n            base_url <>\n              \"/?error=\" <>\n              URI.encode(\"twitter login callback failed for some reason, tell ben to check logs\")\n          )\n      end\n    rescue\n      e ->\n        Sentry.capture_exception(e,\n          stacktrace: __STACKTRACE__,\n          extra: %{twitter_auth: \"/callback\"}\n        )\n\n        conn_with_qp\n        |> Broth.Plugs.Redirect.redirect(\n          base_url <>\n            \"/?error=\" <>\n            URI.encode(\"auth failed, enable cookies and try again or give GitHub a try\")\n        )\n    end\n  end\n\n  def get_base_url(conn) do\n    case conn |> get_session(:redirect_to_next) do\n      true ->\n        \"https://next.dogehouse.tv\"\n\n      _ ->\n        Application.fetch_env!(:kousa, :web_url)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/routes/user.ex",
    "content": "defmodule Broth.Routes.User do\n  import Plug.Conn\n\n  alias Beef.Users\n\n  use Plug.Router\n\n  plug(Broth.Plugs.Cors)\n  plug(:match)\n  plug(:dispatch)\n\n  get \"/:username\" do\n    %Plug.Conn{params: %{\"username\" => username}} = conn\n\n    conn\n    |> put_resp_content_type(\"application/json\")\n    |> send_resp(\n      200,\n      Jason.encode!(%{user: Users.get_by_username(username)})\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/socket_handler.ex",
    "content": "defmodule Broth.SocketHandler do\n  require Logger\n  import Kousa.Utils.Version\n\n  defstruct user: nil,\n            ip: nil,\n            encoding: nil,\n            compression: nil,\n            version: nil,\n            callers: [],\n            user_ids_i_am_blocking: [],\n            whisperPrivacySetting: nil\n\n  @type state :: %__MODULE__{\n          user: nil | Beef.Schemas.User.t(),\n          ip: String.t(),\n          encoding: :etf | :json,\n          compression: nil | :zlib,\n          version: Version.t(),\n          user_ids_i_am_blocking: [String.t()],\n          callers: [pid]\n        }\n\n  @behaviour :cowboy_websocket\n\n  ###############################################################\n  ## initialization boilerplate\n\n  @impl true\n  def init(request, _state) do\n    props = :cowboy_req.parse_qs(request)\n\n    compression =\n      case :proplists.get_value(\"compression\", props) do\n        p when p in [\"zlib_json\", \"zlib\"] -> :zlib\n        _ -> nil\n      end\n\n    encoding =\n      case :proplists.get_value(\"encoding\", props) do\n        \"etf\" -> :etf\n        _ -> :json\n      end\n\n    ip = request.headers[\"x-forwarded-for\"]\n\n    state = %__MODULE__{\n      ip: ip,\n      user_ids_i_am_blocking: [],\n      whisperPrivacySetting: :on,\n      encoding: encoding,\n      compression: compression,\n      callers: get_callers(request)\n    }\n\n    {:cowboy_websocket, request, state}\n  end\n\n  @auth_timeout Application.compile_env(:kousa, :websocket_auth_timeout)\n\n  @impl true\n  def websocket_init(state) do\n    Process.send_after(self(), :auth_timeout, @auth_timeout)\n    Process.put(:\"$callers\", state.callers)\n\n    {:ok, state}\n  end\n\n  #######################################################################\n  ## API\n\n  @typep command :: :cow_ws.frame() | {:shutdown, :normal}\n  @typep call_result :: {[command], state}\n\n  # exit\n  def exit(pid), do: send(pid, :exit)\n  @spec exit_impl(state) :: call_result\n  defp exit_impl(state) do\n    # note the remote webserver will then close the connection.  The\n    # second command forces a shutdown in case the client is a jerk and\n    # tries to DOS us by holding open connections.\n    # frontend expects 4003\n    ws_push([{:close, 4003, \"killed by server\"}, shutdown: :normal], state)\n  end\n\n  # auth timeout\n  @spec auth_timeout_impl(state) :: call_result\n  defp auth_timeout_impl(state) do\n    if state.user do\n      ws_push(nil, state)\n    else\n      ws_push([{:close, 1000, \"authorization\"}, shutdown: :normal], state)\n    end\n  end\n\n  # unsub from PubSub topic\n  def unsub(socket, topic), do: send(socket, {:unsub, topic})\n\n  alias Onion.PubSub\n\n  defp unsub_impl(topic, state) do\n    PubSub.unsubscribe(topic)\n    ws_push(nil, state)\n  end\n\n  # transitional remote_send message\n  def remote_send(socket, message), do: send(socket, {:remote_send, message})\n\n  @spec remote_send_impl(Kousa.json(), state) :: call_result\n  defp remote_send_impl(message, state) do\n    ws_push(prepare_socket_msg(message, state), state)\n  end\n\n  @special_cases ~w(\n    block_user_and_from_room\n    fetch_follow_list\n    join_room_and_get_info\n  )\n\n  ##########################################################################\n  ## USER UPDATES\n\n  def user_update_impl({\"user:update:\" <> user_id, user}, state = %{user: %{id: user_id}}) do\n    %Broth.Message{operator: \"user:update\", payload: user}\n    |> adopt_version(state)\n    |> prepare_socket_msg(state)\n    |> ws_push(%{state | user: user})\n  end\n\n  def user_update_impl(_, state), do: ws_push(nil, state)\n\n  ##########################################################################\n  ## CHAT MESSAGES\n\n  defp real_chat_impl(\n         {\"chat:\" <> _room_id, message},\n         %__MODULE__{} = state\n       ) do\n    # TODO: make this guard against room_id or self_id when we put room into the state.\n    message\n    |> adopt_version(state)\n    |> prepare_socket_msg(state)\n    |> ws_push(state)\n  end\n\n  def chat_impl(\n        {\"chat:\" <> _room_id,\n         %Broth.Message{payload: %Broth.Message.Chat.Send{from: from, isWhisper: isWhisper}}} =\n          p1,\n        %__MODULE__{} = state\n      ) do\n    if (isWhisper == true and not is_nil(state.user) and state.user.whisperPrivacySetting == :off) or\n         Enum.any?(state.user_ids_i_am_blocking, &(&1 == from)) do\n      ws_push(nil, state)\n    else\n      real_chat_impl(p1, state)\n    end\n  end\n\n  def chat_impl(\n        {\"chat:\" <> _room_id, _} = p1,\n        %__MODULE__{} = state\n      ) do\n    # TODO: make this guard against room_id or self_id when we put room into the state.\n    real_chat_impl(p1, state)\n  end\n\n  def chat_impl(_, state), do: ws_push(nil, state)\n\n  ##########################################################################\n  ## WEBSOCKET API\n\n  @impl true\n  def websocket_handle({:text, \"ping\"}, state), do: {[text: \"pong\"], state}\n\n  # this is for firefox\n  @impl true\n  def websocket_handle({:ping, _}, state), do: {[text: \"pong\"], state}\n\n  def websocket_handle({:text, command_json}, state) do\n    with {:ok, message_map!} <- Jason.decode(command_json),\n         # temporary trap mediasoup direct commands\n         %{\"op\" => <<not_at>> <> _} when not_at != ?@ <- message_map!,\n         # temporarily trap special cased commands (to go by version 0.3.0)\n         %{\"op\" => not_special_case} when not_special_case not in @special_cases <- message_map!,\n         # translation from legacy maps to new maps\n         message_map! = Broth.Translator.translate_inbound(message_map!),\n         {:ok, message = %{errors: nil}} <- validate(message_map!, state),\n         :ok <- auth_check(message, state) do\n      # make the state adopt the version of the inbound message.\n      new_state =\n        if message.operator == Broth.Message.Auth.Request do\n          adopt_version(state, message)\n        else\n          state\n        end\n\n      dispatch(message, new_state)\n    else\n      # special cases: mediasoup operations\n      msg = %{\"op\" => \"@\" <> _} ->\n        dispatch_mediasoup_message(msg, state)\n        ws_push(nil, state)\n\n      # legacy special cases\n      msg = %{\"op\" => special_case} when special_case in @special_cases ->\n        msg\n        |> Broth.LegacyHandler.process(state)\n        |> ws_push(adopt_version(state, %{version: ~v(0.1.0)}))\n\n      {:error, :auth} ->\n        ws_push({:close, 4004, \"not_authenticated\"}, state)\n\n      {:error, %Jason.DecodeError{}} ->\n        ws_push({:close, 4001, \"invalid input\"}, state)\n\n      # error validating the inner changeset.\n      {:ok, error} ->\n        error\n        |> Map.put(:operator, error.inbound_operator)\n        |> prepare_socket_msg(state)\n        |> ws_push(state)\n\n      {:error, changeset = %Ecto.Changeset{}} ->\n        %{errors: Kousa.Utils.Errors.changeset_errors(changeset)}\n        |> prepare_socket_msg(state)\n        |> ws_push(state)\n    end\n  end\n\n  import Ecto.Changeset\n\n  @spec validate(map, state) :: {:ok, Broth.Message.t()} | {:error, Ecto.Changeset.t()}\n  def validate(message, state) do\n    message\n    |> Broth.Message.changeset(state)\n    |> apply_action(:validate)\n  end\n\n  def auth_check(%{operator: op}, state), do: op.auth_check(state)\n\n  def dispatch(message, state) do\n    case message.operator.execute(message.payload, state) do\n      close when elem(close, 0) == :close ->\n        ws_push(close, state)\n\n      {:error, err} ->\n        message\n        |> wrap_error(err)\n        |> prepare_socket_msg(state)\n        |> ws_push(state)\n\n      {:error, errors, new_state} ->\n        message\n        |> wrap_error(errors)\n        |> prepare_socket_msg(new_state)\n        |> ws_push(new_state)\n\n      {:noreply, new_state} ->\n        ws_push(nil, new_state)\n\n      {:reply, payload, new_state} ->\n        message\n        |> wrap(payload)\n        |> prepare_socket_msg(new_state)\n        |> ws_push(new_state)\n    end\n  end\n\n  def wrap(message, payload = %{}) do\n    %{message | operator: message.inbound_operator <> \":reply\", payload: payload}\n  end\n\n  defp wrap_error(message, error) do\n    Map.merge(\n      message,\n      %{\n        payload: nil,\n        operator: message.inbound_operator,\n        errors: to_map(error)\n      }\n    )\n  end\n\n  # we expect three types of errors:\n  # - Changeset errors\n  # - textual errors\n  # - anything else\n  # this common `to_map` function handles them all.\n\n  defp to_map(changeset = %Ecto.Changeset{}) do\n    Kousa.Utils.Errors.changeset_errors(changeset)\n  end\n\n  defp to_map(string) when is_binary(string) do\n    %{message: string}\n  end\n\n  defp to_map(other) do\n    %{message: inspect(other)}\n  end\n\n  defp dispatch_mediasoup_message(msg, %{user: %{id: user_id}}) do\n    with {:ok, room_id} <- Beef.Users.tuple_get_current_room_id(user_id),\n         [{_, _}] <- Onion.RoomSession.lookup(room_id) do\n      voice_server_id = Onion.RoomSession.get(room_id, :voice_server_id)\n\n      mediasoup_message =\n        msg\n        |> Map.put(\"d\", msg[\"p\"] || msg[\"d\"])\n        |> put_in([\"d\", \"peerId\"], user_id)\n        # voice server expects this key\n        |> put_in([\"uid\"], user_id)\n        |> put_in([\"d\", \"roomId\"], room_id)\n\n      Onion.VoiceRabbit.send(voice_server_id, mediasoup_message)\n    end\n\n    # if this results in something funny because the user isn't in a room, we\n    # will just swallow the result, it means that there is some amount of asynchrony\n    # in the information about who is in what room.\n  end\n\n  def prepare_socket_msg(data, state) do\n    data\n    |> encode_data(state)\n    |> prepare_data(state)\n  end\n\n  defp encode_data(data, %{encoding: :etf}) do\n    data\n    |> Map.from_struct()\n    |> :erlang.term_to_binary()\n  end\n\n  defp encode_data(data, %{encoding: :json}) do\n    Jason.encode!(data)\n  end\n\n  defp prepare_data(data, %{compression: :zlib}) do\n    z = :zlib.open()\n\n    :zlib.deflateInit(z)\n    data = :zlib.deflate(z, data, :finish)\n    :zlib.deflateEnd(z)\n\n    {:binary, data}\n  end\n\n  defp prepare_data(data, %{encoding: :etf}) do\n    {:binary, data}\n  end\n\n  defp prepare_data(data, %{encoding: :json}) do\n    {:text, data}\n  end\n\n  def ws_push(frame, state) do\n    {List.wrap(frame), state}\n  end\n\n  def adopt_version(target = %{version: _}, %{version: version}) do\n    %{target | version: version}\n  end\n\n  ########################################################################\n  # test helper functions\n\n  if Mix.env() == :test do\n    defp get_callers(request) do\n      request_bin = :cowboy_req.header(\"user-agent\", request)\n\n      List.wrap(\n        if is_binary(request_bin) do\n          request_bin\n          |> Base.decode16!()\n          |> :erlang.binary_to_term()\n        end\n      )\n    end\n  else\n    defp get_callers(_), do: []\n  end\n\n  # ROUTER\n\n  @impl true\n  def websocket_info({:EXIT, _, _}, state), do: exit_impl(state)\n  def websocket_info(:exit, state), do: exit_impl(state)\n  def websocket_info(:auth_timeout, state), do: auth_timeout_impl(state)\n  def websocket_info({:remote_send, message}, state), do: remote_send_impl(message, state)\n  def websocket_info({:unsub, topic}, state), do: unsub_impl(topic, state)\n  def websocket_info(message = {\"chat:\" <> _, _}, state), do: chat_impl(message, state)\n\n  def websocket_info(message = {\"user:update:\" <> _, _}, state),\n    do: user_update_impl(message, state)\n\n  # throw out all other messages\n  def websocket_info(_, state) do\n    ws_push(nil, state)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/broth/translator/v0_1_0.ex",
    "content": "defmodule Broth.Translator.V0_1_0 do\n  @moduledoc \"\"\"\n  Version 0.1.0 -> 0.2.0 translation\n  \"\"\"\n\n  import Kousa.Utils.Version, only: [sigil_v: 2]\n\n  ############################################################################\n  ## INBOUND MESSAGES\n\n  @operator_translations %{\n    \"send_room_chat_msg\" => \"chat:send_msg\",\n    \"invite_to_room\" => \"room:invite\",\n    \"get_my_following\" => \"user:get_following\",\n    \"get_top_public_rooms\" => \"room:get_top\",\n    \"get_blocked_from_room_users\" => \"room:get_banned_users\",\n    \"mute\" => \"room:mute\",\n    \"deafen\" => \"room:deafen\",\n    \"delete_room_chat_message\" => \"chat:delete\",\n    \"auth\" => \"auth:request\",\n    \"leave_room\" => \"room:leave\",\n    \"create_room\" => \"room:create\",\n    \"edit_profile\" => \"user:update\",\n    \"ban\" => \"user:ban\",\n    \"set_listener\" => \"room:set_role\",\n    \"add_speaker\" => \"room:set_role\",\n    \"change_mod_status\" => \"room:set_auth\",\n    \"change_room_creator\" => \"room:set_auth\",\n    \"make_room_public\" => \"room:update\",\n    \"edit_room\" => \"room:update\",\n    \"get_invite_list\" => \"room:get_invite_list\",\n    \"get_user_profile\" => \"user:get_info\",\n    \"ask_to_speak\" => \"room:set_role\",\n    \"ban_from_room_chat\" => \"chat:ban\",\n    \"block_from_room\" => \"room:ban\",\n    \"follow_info\" => \"user:get_relationship\",\n    \"speaking_change\" => \"room:set_active_speaker\",\n    \"get_my_scheduled_rooms_about_to_start\" => \"room:get_scheduled\",\n    \"get_scheduled_rooms\" => \"room:get_scheduled\",\n    \"delete_scheduled_room\" => \"room:delete_scheduled\",\n    \"edit_scheduled_room\" => \"room:update_scheduled\",\n    \"schedule_room\" => \"room:create_scheduled\",\n    \"create_room_from_scheduled_room\" => \"room:create\",\n    \"unban_from_room\" => \"room:unban\",\n    \"search\" => \"misc:search\",\n    \"unban_from_room_chat\" => \"chat:unban\",\n    # follow needs to arbitrate if it becomes follow or unfollow.\n    \"follow\" => nil,\n    # get_follow_list needs to arbitrate if its followers or following.\n    \"get_follow_list\" => nil,\n    # these are special cases:\n    \"block_user_and_from_room\" => \"block_user_and_from_room\",\n    \"fetch_follow_list\" => \"fetch_follow_list\",\n    \"join_room_and_get_info\" => \"join_room_and_get_info\"\n  }\n\n  @operators Map.keys(@operator_translations)\n\n  defguard translates(message) when :erlang.map_get(\"op\", message) in @operators\n\n  def translate_inbound(message = %{\"op\" => operator}) do\n    message\n    |> translate_operation\n    |> translate_in_body(operator)\n    |> add_in_ref(operator)\n    |> add_version\n  end\n\n  def translate_operation(message = %{\"op\" => operator}) do\n    put_in(message, [\"op\"], @operator_translations[operator])\n  end\n\n  def translate_in_body(message, \"edit_profile\") do\n    put_in(message, [\"d\"], get_in(message, [\"d\", \"data\"]))\n  end\n\n  def translate_in_body(message, \"get_user_profile\") do\n    put_in(message, [\"d\", \"userIdOrUsername\"], get_in(message, [\"d\", \"userId\"]))\n  end\n\n  def translate_in_body(message, \"create_room_from_scheduled_room\") do\n    put_in(message, [\"d\", \"scheduledRoomId\"], get_in(message, [\"d\", \"id\"]))\n  end\n\n  def translate_in_body(message, \"create_room\") do\n    is_private = get_in(message, [\"d\", \"privacy\"]) == \"private\"\n    put_in(message, [\"d\", \"isPrivate\"], is_private)\n  end\n\n  def translate_in_body(message, \"ban\") do\n    username = get_in(message, [\"d\", \"username\"])\n    reason = get_in(message, [\"d\", \"reason\"])\n\n    message\n    |> put_in([\"d\", \"userId\"], Beef.Users.get_by_username(username).id)\n    |> put_in([\"d\", \"reason\"], reason)\n  end\n\n  def translate_in_body(message, \"set_listener\") do\n    put_in(message, [\"d\", \"role\"], \"listener\")\n  end\n\n  def translate_in_body(message, \"add_speaker\") do\n    put_in(message, [\"d\", \"role\"], \"speaker\")\n  end\n\n  def translate_in_body(message, \"change_mod_status\") do\n    role = if get_in(message, [\"d\", \"value\"]), do: \"mod\", else: \"user\"\n    put_in(message, [\"d\", \"level\"], role)\n  end\n\n  def translate_in_body(message, \"change_room_creator\") do\n    put_in(message, [\"d\", \"level\"], \"owner\")\n  end\n\n  def translate_in_body(message, \"make_room_public\") do\n    name = get_in(message, [\"d\", \"newName\"])\n    is_private = get_in(message, [\"d\", \"isPrivate\"]) || false\n\n    message\n    |> put_in([\"d\", \"name\"], name)\n    |> put_in([\"d\", \"isPrivate\"], is_private)\n  end\n\n  def translate_in_body(message, \"edit_room\") do\n    is_private = get_in(message, [\"d\", \"privacy\"]) == \"private\"\n    put_in(message, [\"d\", \"isPrivate\"], is_private)\n  end\n\n  def translate_in_body(message, \"ask_to_speak\") do\n    put_in(message, [\"d\", \"role\"], \"raised_hand\")\n  end\n\n  def translate_in_body(message, \"follow\") do\n    # this one has to also alter the operation.\n    operation = if get_in(message, [\"d\", \"value\"]), do: \"user:follow\", else: \"user:unfollow\"\n    put_in(message, [\"op\"], operation)\n  end\n\n  def translate_in_body(message, \"get_follow_list\") do\n    # this one has to also alter the operation.\n    operation =\n      if get_in(message, [\"d\", \"isFollowing\"]),\n        do: \"user:get_following\",\n        else: \"user:get_followers\"\n\n    put_in(message, [\"op\"], operation)\n  end\n\n  def translate_in_body(message, \"speaking_change\") do\n    active? = get_in(message, [\"d\", \"value\"])\n    put_in(message, [\"d\"], %{\"active\" => active?})\n  end\n\n  def translate_in_body(message, \"mute\") do\n    active? = get_in(message, [\"d\", \"value\"])\n    put_in(message, [\"d\"], %{\"muted\" => active?})\n  end\n\n  def translate_in_body(message, \"deafen\") do\n    active? = get_in(message, [\"d\", \"value\"])\n    put_in(message, [\"d\"], %{\"deafened\" => active?})\n  end\n\n  def translate_in_body(message, \"get_my_scheduled_rooms_about_to_start\") do\n    message\n    |> put_in([\"d\", \"range\"], \"upcoming\")\n    |> put_in([\"d\", \"userId\"], \"self\")\n  end\n\n  def translate_in_body(message, \"get_scheduled_rooms\") do\n    if get_in(message, [\"d\", \"getOnlyMyScheduledRooms\"]) do\n      put_in(message, [\"d\", \"userId\"], \"self\")\n    else\n      message\n    end\n  end\n\n  def translate_in_body(message, \"delete_scheduled_room\") do\n    room_id = get_in(message, [\"d\", \"id\"])\n    put_in(message, [\"d\", \"roomId\"], room_id)\n  end\n\n  def translate_in_body(message, \"edit_scheduled_room\") do\n    room_id = get_in(message, [\"d\", \"id\"])\n\n    room_data =\n      message\n      |> get_in([\"d\", \"data\"])\n      |> Kernel.||(%{})\n      |> Map.put(\"id\", room_id)\n\n    Map.put(message, \"d\", room_data)\n  end\n\n  def translate_in_body(message, _op), do: message\n\n  # these casts need to be instrumented with fetchId in order to be treated\n  # as a cast.\n  @casts_to_calls ~w(auth leave_room ban make_room_public mute deafen)\n\n  def add_in_ref(message, op) when op in @casts_to_calls do\n    Map.put(message, \"fetchId\", UUID.uuid4())\n  end\n\n  def add_in_ref(message, _op), do: message\n\n  def add_version(message), do: Map.put(message, \"version\", ~v(0.1.0))\n\n  ############################################################################\n  ## OUTBOUND MESSAGES\n\n  def translate_outbound(message, original) do\n    %{op: \"fetch_done\", d: message.p}\n    |> add_out_ref(message)\n    |> add_out_err(message)\n    |> translate_out_body(original.inbound_operator || message.op)\n  end\n\n  defp add_out_ref(message, %{ref: ref}), do: Map.put(message, :fetchId, ref)\n  defp add_out_ref(message, _), do: message\n\n  defp add_out_err(message, %{e: err}), do: Map.put(message, :e, err)\n  defp add_out_err(message, _), do: message\n\n  def translate_out_body(message, \"auth:request\") do\n    %{message | op: \"auth-good\", d: %{user: message.d}}\n  end\n\n  def translate_out_body(message, \"user:ban\") do\n    %{message | op: \"ban_done\", d: %{worked: !message[:e]}}\n  end\n\n  def translate_out_body(message, \"room:create\") do\n    %{message | d: %{room: message.d}}\n  end\n\n  def translate_out_body(message = %{e: errors}, \"user:update\") do\n    %{message | d: %{isUsernameTaken: \"has already been taken\" in Map.values(errors)}}\n  end\n\n  def translate_out_body(message, \"user:get_relationship\") do\n    new_data =\n      case message.d.relationship do\n        nil -> %{followsYou: false, youAreFollowing: false}\n        :follower -> %{followsYou: true, youAreFollowing: false}\n        :following -> %{followsYou: false, youAreFollowing: true}\n        :mutual -> %{followsYou: true, youAreFollowing: true}\n      end\n\n    %{message | d: new_data}\n  end\n\n  def translate_out_body(message, \"room:create_scheduled\") do\n    %{message | d: %{scheduledRoom: message.d}}\n  end\n\n  def translate_out_body(message, \"room:update\") do\n    %{message | d: !Map.get(message, :e)}\n  end\n\n  def translate_out_body(message, \"room:get_invite_list\") do\n    data = %{users: message.d.invites, nextCursor: message.d.nextCursor}\n    %{message | d: data}\n  end\n\n  def translate_out_body(message, \"user:get_following\") do\n    data = %{users: message.d.following, nextCursor: message.d.nextCursor}\n    %{message | d: data}\n  end\n\n  def translate_out_body(message, \"user:get_followers\") do\n    data = %{users: message.d.followers, nextCursor: message.d.nextCursor}\n    %{message | d: data}\n  end\n\n  def translate_out_body(message, \"room:leave\") do\n    %{message | op: \"you_left_room\"}\n  end\n\n  def translate_out_body(message, \"room:get_scheduled\") do\n    rooms = message.d.rooms\n    %{message | d: %{\"scheduledRooms\" => rooms}}\n  end\n\n  #################################################################\n  # autogenous messages\n\n  def translate_out_body(message, \"chat:send\") do\n    user_info =\n      message.d.from\n      |> Beef.Users.get_by_id()\n      |> Map.take([:avatarUrl, :displayName, :username])\n\n    chat_msg =\n      message.d\n      |> Map.take([:id, :isWhisper, :sentAt, :tokens])\n      |> Map.merge(user_info)\n      |> Map.put(:userId, message.d.from)\n\n    %{\n      message\n      | d: %{\n          \"msg\" => chat_msg,\n          \"userId\" => message.d.from\n        },\n        op: \"new_chat_msg\"\n    }\n  end\n\n  def translate_out_body(message, \"chat:delete\") do\n    %{op: \"message_deleted\", d: message.d}\n  end\n\n  #################################################################\n  # pure outbound messages\n\n  def translate_out_body(message, _), do: message\nend\n"
  },
  {
    "path": "kousa/lib/broth/translator.ex",
    "content": "defmodule Broth.Translator do\n  import Kousa.Utils.Version\n  alias Broth.Translator.V0_1_0\n  require V0_1_0\n\n  def translate_inbound(message) when V0_1_0.translates(message) do\n    V0_1_0.translate_inbound(message)\n    # pipe into V0_2_0 translation layer here.\n  end\n\n  # add future V0_2_0 abstraction layer here.\n  def translate_inbound(message), do: message\n\n  def translate_outbound(message, original = %{version: ~v(0.1.0)}) do\n    V0_1_0.translate_outbound(message, original)\n  end\n\n  def translate_outbound(message, _), do: message\nend\n"
  },
  {
    "path": "kousa/lib/broth.ex",
    "content": "defmodule Broth do\n  import Plug.Conn\n\n  @type json :: String.t() | number | boolean | nil | [json] | %{String.t() => json}\n\n  alias Broth.Routes.DevOnly\n  alias Broth.Routes.GitHubAuth\n  alias Broth.Routes.TwitterAuth\n  alias Broth.Routes.DiscordAuth\n  alias Broth.Routes.ScheduledRoom\n  alias Broth.Routes.Room\n  alias Broth.Routes.Stats\n  alias Broth.Routes.BotAuth\n  alias Broth.Routes.User\n  use Plug.Router\n\n  if Mix.env() == :test do\n    plug(:set_callers)\n\n    defp get_callers(%Plug.Conn{req_headers: req_headers}) do\n      {_, request_bin} = Enum.find(req_headers, fn {key, _} -> key == \"user-agent\" end)\n\n      List.wrap(\n        if is_binary(request_bin) do\n          request_bin\n          |> Base.decode16!()\n          |> :erlang.binary_to_term()\n        end\n      )\n    end\n\n    defp set_callers(conn, _params) do\n      Process.put(:\"$callers\", get_callers(conn))\n      conn\n    end\n  end\n\n  use Sentry.PlugCapture\n  plug(Broth.Plugs.Cors)\n  plug(Kousa.Metric.PrometheusExporter)\n  plug(:match)\n  plug(:dispatch)\n\n  options _ do\n    send_resp(conn, 200, \"\")\n  end\n\n  forward(\"/auth/github\", to: GitHubAuth)\n  forward(\"/auth/twitter\", to: TwitterAuth)\n  forward(\"/auth/discord\", to: DiscordAuth)\n  # forward(\"/me\", to: Kousa.Me)\n  forward(\"/dev\", to: DevOnly)\n  forward(\"/scheduled-room\", to: ScheduledRoom)\n  forward(\"/room\", to: Room)\n  forward(\"/user\", to: User)\n  forward(\"/stats\", to: Stats)\n  forward(\"/bot\", to: BotAuth)\n\n  get _ do\n    send_resp(conn, 404, \"not found\")\n  end\n\n  post _ do\n    send_resp(conn, 404, \"not found\")\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/access_token.ex",
    "content": "defmodule Kousa.AccessToken do\n  def __default_signer__,\n    do: Joken.Signer.create(\"HS256\", Application.fetch_env!(:kousa, :access_token_secret))\n\n  use Joken.Config\n\n  # 1 hour\n  def token_config, do: default_claims(default_exp: 60 * 60)\nend\n"
  },
  {
    "path": "kousa/lib/kousa/auth.ex",
    "content": "defmodule Kousa.Auth do\n  alias Onion.PubSub\n\n  alias Kousa.Utils.TokenUtils\n\n  @spec authenticate(Broth.Message.Auth.Request.t(), IP.addr()) ::\n          {:ok, Beef.Schemas.User.t()} | {:error, term}\n  def authenticate(request, ip) do\n    case TokenUtils.tokens_to_user_id(request.accessToken, request.refreshToken) do\n      nil ->\n        {:error, \"invalid_authentication\"}\n\n      {:existing_claim, user_id} ->\n        do_auth(Beef.Users.get(user_id), nil, request, ip)\n\n      # TODO: streamline this since we're duplicating user_id and user.\n      {:new_tokens, _user_id, tokens, user} ->\n        do_auth(user, tokens, request, ip)\n    end\n  end\n\n  defp do_auth(user, tokens, request, ip) do\n    alias Onion.UserSession\n    alias Onion.RoomSession\n    alias Beef.Rooms\n\n    if user do\n      # note that this will start the session and will be ignored if the\n      # session is already running.\n      UserSession.start_supervised(\n        user_id: user.id,\n        ip: ip,\n        username: user.username,\n        avatar_url: user.avatarUrl,\n        banner_url: user.bannerUrl,\n        display_name: user.displayName,\n        current_room_id: user.currentRoomId,\n        muted: request.muted,\n        deafened: request.deafened,\n        bot_owner_id: user.botOwnerId\n      )\n\n      if user.ip != ip do\n        Beef.Users.set_ip(user.id, ip)\n      end\n\n      # currently we only allow one active websocket connection per-user\n      # at some point soon we're going to make this multi-connection, and we\n      # won't have to do this.\n      UserSession.set_active_ws(user.id, self())\n\n      if tokens do\n        UserSession.new_tokens(user.id, tokens)\n      end\n\n      roomIdFromFrontend = request.currentRoomId\n\n      cond do\n        user.currentRoomId ->\n          # TODO: move toroom business logic\n          room = Rooms.get_room_by_id(user.currentRoomId)\n\n          RoomSession.start_supervised(\n            room_id: user.currentRoomId,\n            voice_server_id: room.voiceServerId,\n            chat_mode: room.chatMode,\n            room_creator_id: room.creatorId\n          )\n\n          PubSub.subscribe(\"chat:\" <> room.id)\n          RoomSession.join_room(room.id, user.id, request.muted, request.deafened)\n\n          if request.reconnectToVoice == true do\n            Kousa.Room.join_vc_room(user.id, room)\n          end\n\n        roomIdFromFrontend ->\n          Kousa.Room.join_room(user.id, roomIdFromFrontend)\n\n        true ->\n          :ok\n      end\n\n      # subscribe to chats directed to oneself.\n      PubSub.subscribe(\"chat:\" <> user.id)\n      # subscribe to user updates\n      PubSub.subscribe(\"user:update:\" <> user.id)\n\n      {:ok, user}\n    else\n      {:close, 4001, \"invalid_authentication\"}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/chat.ex",
    "content": "defmodule Kousa.Chat do\n  alias Beef.Rooms\n  alias Broth.Message.Chat.Delete\n  alias Kousa.Utils.UUID\n  alias Onion.Chat\n\n  def send_msg(payload) do\n    # TODO: pull room information from passed parameters from ws_session.\n    case Beef.Users.get_current_room_id(payload.from) do\n      nil ->\n        :noop\n\n      room_id ->\n        Onion.Chat.send_msg(room_id, payload)\n    end\n\n    :ok\n  end\n\n  @ban_roles [:creator, :mod]\n\n  def ban_user(user_id, user_id_to_ban) do\n    room =\n      case Rooms.get_room_status(user_id) do\n        {role, room = %{creatorId: creator_id}}\n        when role in @ban_roles and creator_id != user_id_to_ban ->\n          room\n\n        _ ->\n          nil\n      end\n\n    if room do\n      Chat.ban_user(room.id, user_id_to_ban)\n      :ok\n    else\n      {:error, \"#{user_id} not authorized to ban #{user_id_to_ban}\"}\n    end\n  end\n\n  def unban_user(user_id, user_id_to_unban) do\n    case Rooms.get_room_status(user_id) do\n      {role, room} when role in @ban_roles ->\n        Chat.unban_user(room.id, user_id_to_unban)\n\n      _ ->\n        nil\n    end\n  end\n\n  @type delete_opts :: [by: UUID.t()]\n  @spec delete_msg(Delete.t(), delete_opts) :: :ok\n  # Delete room chat messages\n  def delete_msg(deletion, opts) do\n    user_id = opts[:by]\n\n    room =\n      case Rooms.get_room_status(user_id) do\n        {:creator, room} ->\n          room\n\n        # Mods cannot delete creator's messages\n        {:mod, room = %{creatorId: creator_id}}\n        when user_id != creator_id ->\n          room\n\n        {:listener, room} when user_id == deletion.userId ->\n          room\n\n        _ ->\n          nil\n      end\n\n    if room do\n      Onion.Chat.delete_message(room.id, deletion)\n    else\n      {:error, \"#{user_id} not authorized to delete the selected message\"}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/discord.ex",
    "content": "defmodule Kousa.Discord do\n  def get_avatar_url(user) do\n    if is_nil(user[\"avatar\"]),\n      do: \"https://cdn.discordapp.com/embed/avatars/\" <> rem(user[\"discriminator\"], 5) <> \".png\",\n      else: \"https://cdn.discordapp.com/avatars/\" <> user[\"id\"] <> \"/\" <> user[\"avatar\"] <> \".png\"\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/follow_logic.ex",
    "content": "defmodule Kousa.Follow do\n  alias Beef.Follows\n  alias Beef.Users\n  alias Beef.UserBlocks\n  alias Beef.Schemas.Follow\n  alias Beef.Schemas.Room\n\n  def get_follow_list(user_id, user_id_to_get_list_for, get_following_list, cursor) do\n    if get_following_list do\n      Follows.get_following(user_id, user_id_to_get_list_for, cursor)\n    else\n      Follows.get_followers(user_id, user_id_to_get_list_for, cursor)\n    end\n  end\n\n  # probably can be refactored into a single db query\n  def get_follow_list_by_username(user_id, username, get_following_list, cursor) do\n    user = Users.get_by_username(username)\n\n    case user do\n      %{id: id} ->\n        if get_following_list do\n          Follows.get_following(user_id, id, cursor)\n        else\n          Follows.get_followers(user_id, id, cursor)\n        end\n\n      _ ->\n        %{users: [], nextCursor: nil}\n    end\n  end\n\n  # TODO: break this out into assertive \"follow\" and \"unfollow\" commands, instead of\n  # ambiguous \"should_follow\"\n  def follow(user_id, user_you_want_to_follow_id, should_follow) do\n    if should_follow do\n      if user_id != user_you_want_to_follow_id and\n           not UserBlocks.blocked?(user_you_want_to_follow_id, user_id) do\n        Follows.insert(%{userId: user_you_want_to_follow_id, followerId: user_id})\n      end\n    else\n      Follows.delete(\n        user_you_want_to_follow_id,\n        user_id\n      )\n    end\n  end\n\n  @spec sync_notify_followers_you_created_a_room(String.t(), Room.t()) :: {:ok}\n  def sync_notify_followers_you_created_a_room(user_id, room) do\n    followers_to_notify = Follows.get_followers_online_and_not_in_a_room(user_id)\n\n    if length(followers_to_notify) > 0 do\n      user = Beef.Users.get_by_id(user_id)\n\n      Enum.each(followers_to_notify, fn %Follow{followerId: followerId} ->\n        Onion.UserSession.send_ws(followerId, nil, %{\n          op: \"someone_you_follow_created_a_room\",\n          d: %{\n            roomId: room.id,\n            roomName: room.name,\n            displayName: user.displayName,\n            username: user.username,\n            avatarUrl: user.avatarUrl,\n            # Here if banner will be included in the refactored someone you followed created a room popup\n            bannerUrl: user.bannerUrl,\n            type: \"someone_you_follow_created_a_room\"\n          }\n        })\n      end)\n    end\n\n    {:ok}\n  end\n\n  @spec notify_followers_you_created_a_room(String.t(), Room.t()) :: {:ok, pid()}\n  def notify_followers_you_created_a_room(user_id, room) do\n    Task.start(fn -> sync_notify_followers_you_created_a_room(user_id, room) end)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/github.ex",
    "content": "defmodule Kousa.Github do\n  def pick_primary_email([]) do\n    nil\n  end\n\n  def pick_primary_email(emails) do\n    primary_email = Enum.find(emails, &(&1[\"primary\"] == true))\n\n    if(is_nil(primary_email),\n      do: Enum.at(emails, 0)[\"email\"],\n      else: primary_email[\"email\"]\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/metrics/prometheus.ex",
    "content": "defmodule Kousa.Metric.PipelineInstrumenter do\n  use Prometheus.PlugPipelineInstrumenter\n\n  def label_value(:request_path, conn) do\n    conn.request_path\n  end\nend\n\ndefmodule Kousa.Metric.PrometheusExporter do\n  use Prometheus.PlugExporter\nend\n\ndefmodule Kousa.Metric.UserSessions do\n  use Prometheus.Metric\n\n  def setup do\n    Gauge.declare(\n      name: :user_sessions,\n      help: \"Number of user sessions running\"\n    )\n  end\n\n  def set(n) do\n    Gauge.set([name: :user_sessions], n)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/refresh_token.ex",
    "content": "defmodule Kousa.RefreshToken do\n  def __default_signer__,\n    do: Joken.Signer.create(\"HS256\", Application.fetch_env!(:kousa, :refresh_token_secret))\n\n  use Joken.Config\n\n  # 30 days\n  def token_config, do: default_claims(default_exp: 60 * 60 * 24 * 30)\nend\n"
  },
  {
    "path": "kousa/lib/kousa/release.ex",
    "content": "defmodule Kousa.Release do\n  @app :kousa\n\n  def migrate do\n    load_app()\n\n    for repo <- repos() do\n      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))\n    end\n  end\n\n  def rollback(repo, version) do\n    load_app()\n    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))\n  end\n\n  defp repos do\n    Application.fetch_env!(@app, :ecto_repos)\n  end\n\n  defp load_app do\n    Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/room.ex",
    "content": "defmodule Kousa.Room do\n  alias Kousa.Utils.VoiceServerUtils\n  alias Beef.Users\n  alias Beef.Follows\n  alias Beef.Rooms\n  # note the following 2 module aliases are on the chopping block!\n  alias Beef.RoomPermissions\n  alias Beef.RoomBlocks\n  alias Onion.PubSub\n  alias Onion.UserSession\n  alias Broth.SocketHandler\n\n  def set_auto_speaker(user_id, value) do\n    if room = Rooms.get_room_by_creator_id(user_id) do\n      Onion.RoomSession.set_auto_speaker(room.id, value)\n    end\n  end\n\n  @spec make_room_public(any, any) :: nil | :ok\n  def make_room_public(user_id, new_name) do\n    # this needs to be refactored if a user can have multiple rooms\n    case Beef.Rooms.set_room_privacy_by_creator_id(user_id, false, new_name) do\n      {1, [room]} ->\n        Onion.RoomSession.broadcast_ws(\n          room.id,\n          %{op: \"room_privacy_change\", d: %{roomId: room.id, name: room.name, isPrivate: false}}\n        )\n\n      _ ->\n        nil\n    end\n  end\n\n  @spec make_room_private(any, any) :: nil | :ok\n  def make_room_private(user_id, new_name) do\n    # this needs to be refactored if a user can have multiple rooms\n    case Rooms.set_room_privacy_by_creator_id(user_id, true, new_name) do\n      {1, [room]} ->\n        Onion.RoomSession.broadcast_ws(\n          room.id,\n          %{op: \"room_privacy_change\", d: %{roomId: room.id, name: room.name, isPrivate: true}}\n        )\n\n      _ ->\n        nil\n    end\n  end\n\n  def invite_to_room(user_id, user_id_to_invite) do\n    user = Beef.Users.get_by_id(user_id)\n\n    if user.currentRoomId && Follows.following_me?(user_id, user_id_to_invite) do\n      # @todo store room name in RoomSession to avoid db lookups\n      room = Rooms.get_room_by_id(user.currentRoomId)\n\n      if not is_nil(room) do\n        Onion.RoomSession.create_invite(\n          user.currentRoomId,\n          user_id_to_invite,\n          %{\n            roomName: room.name,\n            displayName: user.displayName,\n            username: user.username,\n            avatarUrl: user.avatarUrl,\n            bannerUrl: user.bannerUrl,\n            type: \"invite\"\n          }\n        )\n      end\n    end\n  end\n\n  defp internal_kick_from_room(user_id_to_kick, room_id) do\n    case UserSession.lookup(user_id_to_kick) do\n      [{_, _}] ->\n        ws_pid = UserSession.get(user_id_to_kick, :pid)\n\n        if ws_pid do\n          SocketHandler.unsub(ws_pid, \"chat:\" <> room_id)\n        end\n\n      _ ->\n        nil\n    end\n\n    current_room_id = Beef.Users.get_current_room_id(user_id_to_kick)\n\n    if current_room_id == room_id do\n      Rooms.kick_from_room(user_id_to_kick, current_room_id)\n      Onion.RoomSession.kick_from_room(current_room_id, user_id_to_kick)\n    end\n  end\n\n  @spec block_from_room(String.t(), String.t(), boolean()) ::\n          nil\n          | :ok\n          | {:askedToSpeak | :creator | :listener | :mod | nil | :speaker,\n             atom | %{:creatorId => any, optional(any) => any}}\n  def block_from_room(user_id, user_id_to_block_from_room, should_ban_ip \\\\ false) do\n    with {status, room} when status in [:creator, :mod] <-\n           Rooms.get_room_status(user_id) do\n      if room.creatorId != user_id_to_block_from_room do\n        RoomBlocks.upsert(%{\n          modId: user_id,\n          userId: user_id_to_block_from_room,\n          roomId: room.id,\n          ip: if(should_ban_ip, do: Users.get_ip(user_id_to_block_from_room), else: nil)\n        })\n\n        internal_kick_from_room(user_id_to_block_from_room, room.id)\n      end\n    end\n  end\n\n  ###################################################################\n  ## AUTH\n\n  @doc \"\"\"\n  sets the authorization level of the user in the room that they're in.\n  This could be 'user', 'mod', or 'owner'.\n\n  Authorization to do so is pulled from the options `:by` keyword.\n\n  TODO: move room into the opts field, and have it be passed in by the\n  socket.\n  \"\"\"\n  def set_auth(user_id, auth, opts) do\n    room_id = Beef.Users.get_current_room_id(user_id)\n\n    case auth do\n      _ when is_nil(room_id) ->\n        :noop\n\n      :owner ->\n        set_owner(room_id, user_id, opts[:by])\n\n      :mod ->\n        set_mod(room_id, user_id, opts[:by])\n\n      :user ->\n        set_user(room_id, user_id, opts[:by])\n    end\n  end\n\n  ####################################################################\n  # owner\n\n  def set_owner(room_id, user_id, setter_id) do\n    with {:creator, _} <- Rooms.get_room_status(setter_id), {1, _} <- Rooms.replace_room_owner(setter_id, user_id) do\n      Onion.RoomSession.set_room_creator_id(room_id, user_id)\n      internal_set_speaker(setter_id, room_id)\n\n      Onion.RoomSession.broadcast_ws(\n        room_id,\n        %{\n          op: \"new_room_creator\",\n          d: %{roomId: room_id, userId: user_id}\n        }\n      )\n    end\n  end\n\n  ####################################################################\n  # mod\n\n  # only creators can set someone to be mod.\n  defp set_mod(room_id, user_id, setter_id) do\n    # TODO: refactor this to pull from preloads.\n    case Rooms.get_room_status(setter_id) do\n      {:creator, _} ->\n        RoomPermissions.set_is_mod(user_id, room_id, true)\n\n        Onion.Chat.set_can_chat(room_id, user_id)\n\n        Onion.RoomSession.broadcast_ws(\n          room_id,\n          %{\n            op: \"mod_changed\",\n            d: %{roomId: room_id, userId: user_id, isMod: true}\n          }\n        )\n\n      _ ->\n        :noop\n    end\n  end\n\n  ####################################################################\n  # plain user\n\n  # mods can demote their own mod status.\n  defp set_user(room_id, user_id, user_id) do\n    case Rooms.get_room_status(user_id) do\n      {:mod, _} ->\n        RoomPermissions.set_is_mod(user_id, room_id, true)\n\n        Onion.RoomSession.broadcast_ws(\n          room_id,\n          %{\n            op: \"mod_changed\",\n            d: %{roomId: room_id, userId: user_id, isMod: false}\n          }\n        )\n\n      _ ->\n        :noop\n    end\n  end\n\n  # only creators can demote mods\n  defp set_user(room_id, user_id, setter_id) do\n    case Rooms.get_room_status(setter_id) do\n      {:creator, _} ->\n        RoomPermissions.set_is_mod(user_id, room_id, false)\n\n        Onion.RoomSession.broadcast_ws(\n          room_id,\n          %{\n            op: \"mod_changed\",\n            d: %{roomId: room_id, userId: user_id, isMod: false}\n          }\n        )\n\n      _ ->\n        :noop\n    end\n  end\n\n  ####################################################################\n  ## ROLE\n\n  @doc \"\"\"\n  sets the role of the user in the room that they're in.  Authorization\n  to do so is pulled from the options `:by` keyword.\n\n  TODO: move room into the opts field, and have it be passed in by the\n  socket.\n  \"\"\"\n  def set_role(user_id, role, opts) do\n    room_id = Beef.Users.get_current_room_id(user_id)\n\n    case role do\n      _ when is_nil(room_id) ->\n        :noop\n\n      :listener ->\n        set_listener(room_id, user_id, opts[:by])\n\n      :speaker ->\n        set_speaker(room_id, user_id, opts[:by])\n\n      :raised_hand ->\n        set_raised_hand(room_id, user_id, opts[:by])\n    end\n  end\n\n  ####################################################################\n  ## listener\n\n  defp set_listener(nil, _, _), do: :noop\n  # you are always allowed to set yourself as listener\n  defp set_listener(room_id, user_id, user_id) do\n    internal_set_listener(user_id, room_id)\n  end\n\n  defp set_listener(room_id, user_id, setter_id) do\n    # TODO: refactor this to be simpler.  The list of\n    # creators and mods should be in the preloads of the room.\n    with {auth, _} <- Rooms.get_room_status(setter_id),\n         {role, _} <- Rooms.get_room_status(user_id) do\n      if auth == :creator or (auth == :mod and role not in [:creator, :mod]) do\n        internal_set_listener(user_id, room_id)\n      end\n    end\n  end\n\n  defp internal_set_listener(user_id, room_id) do\n    RoomPermissions.make_listener(user_id, room_id)\n    Onion.RoomSession.remove_speaker(room_id, user_id)\n  end\n\n  ####################################################################\n  ## speaker\n\n  defp set_speaker(nil, _, _), do: :noop\n\n  defp set_speaker(room_id, user_id, setter_id) do\n    if not RoomPermissions.asked_to_speak?(user_id, room_id) do\n      :noop\n    else\n      case Rooms.get_room_status(setter_id) do\n        {_, nil} ->\n          :noop\n\n        {:mod, _} ->\n          internal_set_speaker(user_id, room_id)\n\n        {:creator, _} ->\n          internal_set_speaker(user_id, room_id)\n\n        {_, _} ->\n          :noop\n      end\n    end\n  end\n\n  @spec internal_set_speaker(any, any) :: nil | :ok | {:err, {:error, :not_found}}\n  defp internal_set_speaker(user_id, room_id) do\n    case RoomPermissions.set_speaker(user_id, room_id, true) do\n      {:ok, _} ->\n        Onion.Chat.set_can_chat(room_id, user_id)\n        # kind of horrible to have to make a double genserver call\n        # here, we'll have to think about how this works (who owns muting)\n        Onion.RoomSession.add_speaker(\n          room_id,\n          user_id,\n          Onion.UserSession.get(user_id, :muted),\n          Onion.UserSession.get(user_id, :deafened)\n        )\n\n      err ->\n        {:err, err}\n    end\n  catch\n    _, _ ->\n      {:error, \"room not found\"}\n  end\n\n  # only you can raise your own hand\n  defp set_raised_hand(room_id, user_id, setter_id) do\n    if user_id == setter_id do\n      if Onion.RoomSession.get(room_id, :auto_speaker) do\n        internal_set_speaker(user_id, room_id)\n      else\n        case RoomPermissions.ask_to_speak(user_id, room_id) do\n          {:ok, %{isSpeaker: true}} ->\n            internal_set_speaker(user_id, room_id)\n\n          _ ->\n            Onion.RoomSession.broadcast_ws(\n              room_id,\n              %{\n                op: \"hand_raised\",\n                d: %{userId: user_id, roomId: room_id}\n              }\n            )\n        end\n      end\n    end\n  end\n\n  ######################################################################\n  ## UPDATE\n\n  def update(user_id, data) do\n    if room = Rooms.get_room_by_creator_id(user_id) do\n      case Rooms.edit(room.id, data) do\n        ok = {:ok, room} ->\n          Onion.RoomSession.broadcast_ws(room.id, %{\n            op: \"new_room_details\",\n            d: %{\n              name: room.name,\n              description: room.description,\n              chatThrottle: room.chatThrottle,\n              isPrivate: room.isPrivate,\n              roomId: room.id\n            }\n          })\n\n          ok\n\n        error = {:error, _} ->\n          error\n      end\n    end\n  end\n\n  def join_vc_room(user_id, room, speaker? \\\\ nil) do\n    speaker? =\n      if is_nil(speaker?),\n        do:\n          room.creatorId == user_id or\n            RoomPermissions.speaker?(user_id, room.id),\n        else: speaker?\n\n    op =\n      if speaker?,\n        do: \"join-as-speaker\",\n        else: \"join-as-new-peer\"\n\n    Onion.VoiceRabbit.send(room.voiceServerId, %{\n      op: op,\n      d: %{roomId: room.id, peerId: user_id},\n      uid: user_id\n    })\n  end\n\n  @spec create_room(\n          String.t(),\n          String.t(),\n          String.t(),\n          boolean(),\n          String.t() | nil,\n          boolean() | nil\n        ) ::\n          {:error, any}\n          | {:ok, %{room: atom | %{:id => any, :voiceServerId => any, optional(any) => any}}}\n  def create_room(\n        user_id,\n        room_name,\n        room_description,\n        is_private,\n        user_id_to_invite \\\\ nil,\n        auto_speaker \\\\ nil\n      ) do\n    room_id = Users.get_current_room_id(user_id)\n\n    if not is_nil(room_id) do\n      leave_room(user_id, room_id)\n    end\n\n    id = Ecto.UUID.generate()\n\n    case Rooms.create(%{\n           id: id,\n           name: room_name,\n           description: room_description,\n           creatorId: user_id,\n           numPeopleInside: 1,\n           voiceServerId: VoiceServerUtils.get_next_voice_server_id(),\n           isPrivate: is_private\n         }) do\n      {:ok, room} ->\n        Onion.RoomSession.start_supervised(\n          room_id: room.id,\n          voice_server_id: room.voiceServerId,\n          auto_speaker: auto_speaker,\n          chat_throttle: room.chatThrottle,\n          chat_mode: room.chatMode,\n          room_creator_id: room.creatorId\n        )\n\n        muted? = Onion.UserSession.get(user_id, :muted)\n        deafened? = Onion.UserSession.get(user_id, :deafened)\n\n        Onion.RoomSession.join_room(room.id, user_id, muted?, deafened?, no_fan: true)\n\n        Onion.VoiceRabbit.send(room.voiceServerId, %{\n          op: \"create-room\",\n          d: %{roomId: id},\n          uid: user_id\n        })\n\n        join_vc_room(user_id, room, true)\n\n        if not is_private do\n          Kousa.Follow.notify_followers_you_created_a_room(user_id, room)\n        end\n\n        if not is_nil(user_id_to_invite) do\n          # TODO: change this to Task.Supervised\n          Task.start(fn ->\n            Kousa.Room.invite_to_room(user_id, user_id_to_invite)\n          end)\n        end\n\n        # subscribe to this room's chat\n        Onion.PubSub.subscribe(\"chat:\" <> id)\n\n        {:ok, %{room: room}}\n\n      {:error, x} ->\n        {:error, Kousa.Utils.Errors.changeset_to_first_err_message_with_field_name(x)}\n    end\n  end\n\n  # NB this function does not correctly return an updated room struct if the\n  # action is valid.\n\n  # NB2, this function has an non-idiomatic parameter order.  room_id should\n  # come first.\n  def join_room(user_id, room_id) do\n    currentRoomId = Beef.Users.get_current_room_id(user_id)\n\n    if currentRoomId == room_id do\n      %{room: Rooms.get_room_by_id(room_id)}\n    else\n      case Rooms.can_join_room(room_id, user_id) do\n        {:error, message} ->\n          %{error: message}\n\n        {:ok, room} ->\n          # private rooms can now be joined by anyone who has the link\n          # they are functioning closer to an \"unlisted\" room\n          if currentRoomId do\n            leave_room(user_id, currentRoomId)\n          end\n\n          # subscribe to the new room chat\n          PubSub.subscribe(\"chat:\" <> room_id)\n\n          updated_user = Rooms.join_room(room, user_id)\n\n          {muted, deafened} =\n            case Onion.UserSession.lookup(user_id) do\n              [{_, _}] ->\n                {\n                  Onion.UserSession.get(user_id, :muted),\n                  Onion.UserSession.get(user_id, :deafened)\n                }\n\n              _ ->\n                {false, false}\n            end\n\n          Onion.RoomSession.join_room(room_id, user_id, muted, deafened)\n\n          canSpeak =\n            case updated_user do\n              %{roomPermissions: %{isSpeaker: true}} -> true\n              _ -> false\n            end\n\n          join_vc_room(user_id, room, canSpeak || room.isPrivate)\n          %{room: room}\n      end\n    end\n  catch\n    _, _ ->\n      {:error, \"that room doesn't exist\"}\n  end\n\n  def leave_room(user_id, current_room_id \\\\ nil) do\n    current_room_id =\n      if is_nil(current_room_id),\n        do: Beef.Users.get_current_room_id(user_id),\n        else: current_room_id\n\n    if current_room_id do\n      case Rooms.leave_room(user_id, current_room_id) do\n        # the room should be destroyed\n        {:bye, room} ->\n          Onion.RoomSession.destroy(current_room_id, user_id)\n\n          Onion.VoiceRabbit.send(room.voiceServerId, %{\n            op: \"destroy-room\",\n            uid: user_id,\n            d: %{peerId: user_id, roomId: current_room_id}\n          })\n\n        # the room stays alive with new room creator\n        x ->\n          case x do\n            {:new_creator_id, creator_id} ->\n              Onion.RoomSession.broadcast_ws(\n                current_room_id,\n                %{op: \"new_room_creator\", d: %{roomId: current_room_id, userId: creator_id}}\n              )\n\n            _ ->\n              nil\n          end\n\n          Onion.RoomSession.leave_room(current_room_id, user_id)\n      end\n\n      # unsubscribe to the room chat\n      PubSub.unsubscribe(\"chat:\" <> current_room_id)\n\n      {:ok, %{roomId: current_room_id}}\n    else\n      {:error, \"you are not in a room\"}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/room_block.ex",
    "content": "defmodule Kousa.RoomBlock do\n  alias Beef.Users\n  alias Beef.Rooms\n  alias Beef.RoomBlocks\n\n  def unban(user_id, user_id_to_unban) do\n    with {:ok, id} when not is_nil(id) <- Users.tuple_get_current_room_id(user_id),\n         true <- Rooms.owner?(id, user_id) do\n      RoomBlocks.unban(id, user_id_to_unban)\n    else\n      _ -> nil\n    end\n  end\n\n  @spec get_blocked_users(any, any) ::\n          false | {:err | nil | list, nil | number | {:error, :not_found}}\n  def get_blocked_users(user_id, offset) do\n    with {:ok, id} when not is_nil(id) <- Users.tuple_get_current_room_id(user_id),\n         true <- Rooms.owner?(id, user_id) do\n      RoomBlocks.get_blocked_users(id, offset)\n    else\n      _ -> nil\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/scheduled_room.ex",
    "content": "defmodule Kousa.ScheduledRoom do\n  alias Kousa.Utils.Errors\n  alias Beef.Schemas.ScheduledRoom\n  alias Beef.ScheduledRooms\n\n  def create_room_from_scheduled_room(user_id, scheduled_room_id, name, description) do\n    case Kousa.Room.create_room(user_id, name, description, false) do\n      {:ok, response} ->\n        ScheduledRooms.room_started(user_id, scheduled_room_id, response.room.id)\n        {:ok, response}\n\n      error ->\n        error\n    end\n  end\n\n  def delete(user_id, id) do\n    ScheduledRooms.delete(user_id, id)\n  end\n\n  def edit(user_id, id, data) do\n    case ScheduledRooms.edit(user_id, id, data) do\n      :ok ->\n        :ok\n\n      {:error, err} ->\n        {:error, Errors.changeset_to_first_err_message(err)}\n    end\n  end\n\n  @spec schedule(String.t(), any) :: {:ok, ScheduledRoom.t()} | {:error, Ecto.Changeset.t()}\n  def schedule(user_id, data) do\n    # @todo add to followers notifications\n\n    case ScheduledRooms.insert(Map.put(data, \"creatorId\", user_id)) do\n      {:ok, scheduled_room} ->\n        {:ok, scheduled_room}\n\n      {:error, err} ->\n        {:error, Errors.changeset_to_first_err_message(err)}\n    end\n  end\n\n  @spec get_scheduled_rooms(binary, boolean, String.t() | nil) ::\n          {[ScheduledRoom], nil | number}\n  def get_scheduled_rooms(user_id, get_only_my_scheduled_rooms, cursor) do\n    ScheduledRooms.get_feed(user_id, get_only_my_scheduled_rooms, cursor)\n  end\n\n  def get_my_scheduled_rooms_about_to_start(user_id) do\n    ScheduledRooms.get_my_scheduled_rooms_about_to_start(user_id)\n  end\n\n  def get_my_scheduled_room(user_id) do\n    ScheduledRooms.get_mine(user_id)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/user.ex",
    "content": "defmodule Kousa.User do\n  alias Beef.Users\n  alias Beef.Schemas.User\n  alias Onion.PubSub\n\n  def delete(user_id) do\n    Kousa.Room.leave_room(user_id)\n    Users.delete(user_id)\n  end\n\n  def revoke_api_key(user_id, bot_id) do\n    user = Users.get_by_id(bot_id)\n\n    if user.id != user_id and user.botOwnerId != user_id do\n      {:error, \"not authorized\"}\n    else\n      new_api_key = UUID.uuid4()\n\n      case user\n           |> User.api_key_changeset(%{apiKey: new_api_key})\n           |> Users.update() do\n        {:ok, _} ->\n          {:ok, new_api_key}\n\n        error ->\n          error\n      end\n    end\n  end\n\n  def update_with(changeset = %Ecto.Changeset{}) do\n    case Users.update(changeset) do\n      {:ok, user} ->\n        # TODO: clean this up by making Onion.UserSession adopt the User schema and having it\n        # accept pubsub broadcast messages.\n\n        Onion.UserSession.set_state(\n          user.id,\n          %{\n            display_name: user.displayName,\n            username: user.username,\n            avatar_url: user.avatarUrl,\n            banner_url: user.bannerUrl\n          }\n        )\n\n        PubSub.broadcast(\"user:update:\" <> user.id, user)\n        {:ok, user}\n\n      {:error, %Ecto.Changeset{errors: [username: {\"has already been taken, _\"}]}} ->\n        {:error, \"that user name is taken\"}\n\n      error ->\n        error\n    end\n  end\n\n  @doc \"\"\"\n  bans a user from the platform.  Must be an admin operator (currently ben) to run\n  this function.  Authorization passed in via the opts (:admin_id) field.\n\n  If someone that isn't ben tries to use it, it won't leak a meaningful error message\n  (to prevent side channel knowledge of authorization status)\n  \"\"\"\n  def ban(user_id_to_ban, reason_for_ban, opts) do\n    authorized_github_id = Application.get_env(:kousa, :ben_github_id, \"\")\n\n    with %{githubId: ^authorized_github_id} <- Users.get_by_id(opts[:admin_id]),\n         user_to_ban = %{} <- Users.get_by_id(user_id_to_ban) do\n      Kousa.Room.leave_room(user_id_to_ban, user_to_ban.currentRoomId)\n      Users.set_reason_for_ban(user_id_to_ban, reason_for_ban)\n      Onion.UserSession.send_ws(user_id_to_ban, nil, %{op: \"banned\", d: %{}})\n      :ok\n    else\n      _ -> {:error, \"tried to ban #{user_id_to_ban} but that user didn't exist\"}\n    end\n  end\n\n  def admin_update_with(changeset, admin) do\n    authorized_github_id = Application.get_env(:kousa, :ben_github_id, \"\")\n\n    if admin.staff == true or admin.githubId == authorized_github_id do\n      case Users.update(changeset) do\n        {:ok, user} ->\n          {:ok, user}\n\n        error ->\n          error\n      end\n    else\n      {:error, \"not authorized\"}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/user_block.ex",
    "content": "defmodule Kousa.UserBlock do\n  alias Beef.UserBlocks\n  alias Beef.Follows\n\n  def block(user_id, user_id_to_block) do\n    Follows.delete(user_id, user_id_to_block)\n    UserBlocks.insert(%{userId: user_id, userIdBlocked: user_id_to_block})\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/errors.ex",
    "content": "defmodule Kousa.Utils.Errors do\n  @spec changeset_errors(Ecto.Changeset.t()) :: map\n  def changeset_errors(%{errors: errors}) do\n    Map.new(errors, fn {k, {message, _}} -> {k, message} end)\n  end\n\n  def changeset_to_first_err_message(%{errors: [{_, {message, values}}]}) do\n    Enum.reduce(values, message, fn {k, v}, acc ->\n      String.replace(acc, \"%{#{k}}\", to_string(v))\n    end)\n  end\n\n  def changeset_to_first_err_message(changeset) do\n    Jason.encode!(changeset)\n  end\n\n  def changeset_to_first_err_message_with_field_name(%{errors: [{field, {message, values}}]}) do\n    error =\n      Kousa.Utils.Errors.changeset_to_first_err_message(%{errors: [{field, {message, values}}]})\n\n    to_string(field) <> \" \" <> error\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/pagination.ex",
    "content": "defmodule Kousa.Utils.Pagination do\n  @spec items_to_offset_tuple(list, pos_integer, pos_integer) :: {list, nil | number}\n  def items_to_offset_tuple(items, offset, limit) do\n    {Enum.slice(items, 0, -1 + limit),\n     if(length(items) == limit, do: -1 + offset + limit, else: nil)}\n  end\n\n  def items_to_cursor_tuple([], _limit, _item_to_cursor) do\n    {[], nil}\n  end\n\n  @spec items_to_cursor_tuple(list, pos_integer, (any -> String.t())) :: {list, nil | String.t()}\n  def items_to_cursor_tuple(items, limit, item_to_cursor) do\n    {Enum.slice(items, 0, -1 + limit),\n     if(length(items) == limit,\n       do: Enum.at(items, limit - 2) |> item_to_cursor.(),\n       else: nil\n     )}\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/random.ex",
    "content": "defmodule Kousa.Utils.Random do\n  def ascii_id() do\n    min = String.to_integer(\"100000\", 36)\n    max = String.to_integer(\"ZZZZZZ\", 36)\n\n    max\n    |> Kernel.-(min)\n    |> :rand.uniform()\n    |> Kernel.+(min)\n    |> Integer.to_string(36)\n  end\n\n  def big_ascii_id() do\n    ascii_id() <> ascii_id()\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/reg_utils.ex",
    "content": "\n"
  },
  {
    "path": "kousa/lib/kousa/utils/token_utils.ex",
    "content": "defmodule Kousa.Utils.TokenUtils do\n  alias Beef.Schemas.User\n  alias Kousa.Utils.UUID\n\n  # TODO: refactor this\n  @type new_tokens :: %{\n          accessToken: String.t(),\n          refreshToken: String.t()\n        }\n\n  @type new_token_result :: {\n          :new_tokens,\n          UUID.t(),\n          new_tokens(),\n          Beef.Schemas.User.t()\n        }\n\n  # TODO: this is a bit of a hacky way to label our types.  this will have to be\n  # revisited in a future revision.\n  @type token_result ::\n          nil\n          | {:existing_claim, term}\n          | new_token_result()\n\n  @spec tokens_to_user_id(String.t(), String.t()) :: token_result\n  def tokens_to_user_id(access_token!, refresh_token) do\n    access_token! = access_token! || \"\"\n\n    case Kousa.AccessToken.verify_and_validate(access_token!) do\n      {:ok, claims} ->\n        {:existing_claim, claims[\"userId\"]}\n\n      _ ->\n        verify_refresh_token(refresh_token)\n    end\n  end\n\n  @spec verify_refresh_token(String.t()) :: new_token_result() | nil\n  defp verify_refresh_token(refresh_token!) do\n    refresh_token! = refresh_token! || \"\"\n\n    case Kousa.RefreshToken.verify_and_validate(refresh_token!) do\n      {:ok, refreshClaims} ->\n        user = Beef.Repo.get(User, refreshClaims[\"userId\"])\n\n        if user &&\n             !user.reasonForBan &&\n             user.tokenVersion == refreshClaims[\"tokenVersion\"] do\n          {:new_tokens, user.id, create_tokens(user), user}\n        end\n\n      _ ->\n        nil\n    end\n  end\n\n  def create_tokens(user) do\n    %{\n      accessToken: Kousa.AccessToken.generate_and_sign!(%{\"userId\" => user.id}),\n      refreshToken:\n        Kousa.RefreshToken.generate_and_sign!(%{\n          \"userId\" => user.id,\n          \"tokenVersion\" => user.tokenVersion\n        })\n    }\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/urls.ex",
    "content": "defmodule Kousa.Utils.Urls do\n  def next_site_url?(url) do\n    case URI.parse(url) do\n      %URI{\n        host: \"next.dogehouse.tv\"\n      } ->\n        true\n\n      _ ->\n        false\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/uuid.ex",
    "content": "defmodule Kousa.Utils.UUID do\n  @type t :: <<_::288>>\n\n  # TODO: consider enforcing UUID4\n  @hex Enum.to_list(?0..?9) ++ Enum.to_list(?a..?f) ++ Enum.to_list(?A..?F)\n\n  @spec normalize(t | nil) :: {:ok, t | nil} | :invalid\n  def normalize(\n        <<a00, a01, a02, a03, a04, a05, a06, a07, ?-, a08, a09, a10, a11, ?-, a12, a13, a14, a15,\n          ?-, a16, a17, a18, a19, ?-, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31>>\n      )\n      when a00 in @hex and a01 in @hex and a02 in @hex and a03 in @hex and\n             a04 in @hex and a05 in @hex and a06 in @hex and a07 in @hex and\n             a08 in @hex and a09 in @hex and a10 in @hex and a11 in @hex and\n             a12 in @hex and a13 in @hex and a14 in @hex and a15 in @hex and\n             a16 in @hex and a17 in @hex and a18 in @hex and a19 in @hex and\n             a20 in @hex and a21 in @hex and a22 in @hex and a23 in @hex and\n             a24 in @hex and a25 in @hex and a26 in @hex and a27 in @hex and\n             a28 in @hex and a29 in @hex and a30 in @hex and a31 in @hex do\n    {:ok,\n     <<lower(a00), lower(a01), lower(a02), lower(a03), lower(a04), lower(a05), lower(a06),\n       lower(a07), ?-, lower(a08), lower(a09), lower(a10), lower(a11), ?-, lower(a12), lower(a13),\n       lower(a14), lower(a15), ?-, lower(a16), lower(a17), lower(a18), lower(a19), ?-, lower(a20),\n       lower(a21), lower(a22), lower(a23), lower(a24), lower(a25), lower(a26), lower(a27),\n       lower(a28), lower(a29), lower(a30), lower(a31)>>}\n  end\n\n  def normalize(nil), do: {:ok, nil}\n\n  def normalize(_), do: :invalid\n\n  defp lower(char) when char in ?A..?F, do: char - 32\n  defp lower(char), do: char\n\n  # for convenience, an Ecto Changeset function\n  import Ecto.Changeset\n\n  def normalize(changeset = %{valid?: false}, _), do: changeset\n\n  def normalize(changeset, field) do\n    changeset\n    |> get_change(field)\n    |> normalize\n    |> case do\n      {:ok, nil} ->\n        changeset\n\n      {:ok, uuid} ->\n        put_change(changeset, field, uuid)\n\n      :invalid ->\n        add_error(changeset, field, \"is invalid\")\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/version.ex",
    "content": "defmodule Kousa.Utils.Version do\n  use Ecto.Type\n\n  def type, do: :string\n\n  def cast(v) when is_binary(v) do\n    Version.parse(v)\n  end\n\n  def cast(v = %Version{}) do\n    {:ok, v}\n  end\n\n  def cast(_), do: :error\n\n  def dump(v = %Version{}) do\n    {:ok, to_string(v)}\n  end\n\n  def dump(_), do: :error\n\n  def load(v) when is_binary(v) do\n    Version.parse(v)\n  end\n\n  defmacro sigil_v({:<<>>, _, [version]}, []) do\n    version\n    |> Version.parse!()\n    |> Macro.escape()\n  end\nend\n\ndefimpl Jason.Encoder, for: Version do\n  def encode(version, _opts) do\n    inspect(to_string(version))\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa/utils/voice_server_utils.ex",
    "content": "defmodule Kousa.Utils.VoiceServerUtils do\n  def idx_to_str_id(n) do\n    case n do\n      0 -> \"\"\n      x -> Integer.to_string(x)\n    end\n  end\n\n  def get_next_voice_server_id() do\n    idx_to_str_id(:rand.uniform(Application.get_env(:kousa, :num_voice_servers, 1)) - 1)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/kousa.ex",
    "content": "defmodule Kousa do\n  use Application\n  #\n  def start(_type, _args) do\n    import Supervisor.Spec, warn: false\n\n    Kousa.Metric.PrometheusExporter.setup()\n    Kousa.Metric.PipelineInstrumenter.setup()\n    Kousa.Metric.UserSessions.setup()\n\n    children = [\n      # top-level supervisor for UserSession group\n      Onion.Supervisors.UserSession,\n      Onion.Supervisors.RoomSession,\n      Onion.Supervisors.Chat,\n      Onion.Supervisors.VoiceRabbit,\n      Onion.Supervisors.VoiceOnlineRabbit,\n      Onion.BotAuthRateLimit,\n      Onion.StatsCache,\n      {Beef.Repo, []},\n      {Phoenix.PubSub, name: Onion.PubSub},\n      Onion.Telemetry,\n      Plug.Cowboy.child_spec(\n        scheme: :http,\n        plug: Broth,\n        options: [\n          port: String.to_integer(System.get_env(\"PORT\") || \"4001\"),\n          dispatch: dispatch(),\n          protocol_options: [idle_timeout: :infinity]\n        ]\n      )\n    ]\n\n    opts = [strategy: :one_for_one, name: Kousa.Supervisor]\n\n    # TODO: make these into tasks\n\n    case Supervisor.start_link(children, opts) do\n      {:ok, pid} ->\n        start_rabbits()\n        start_rooms()\n        {:ok, pid}\n\n      error ->\n        error\n    end\n  end\n\n  defp dispatch do\n    [\n      {:_,\n       [\n         {\"/socket\", Broth.SocketHandler, []},\n         {:_, Plug.Cowboy.Handler, {Broth, []}}\n       ]}\n    ]\n  end\n\n  defp start_rooms() do\n    Enum.each(Beef.Rooms.all_rooms(), fn room ->\n      Onion.RoomSession.start_supervised(\n        room_id: room.id,\n        voice_server_id: room.voiceServerId,\n        chat_mode: room.chatMode,\n        room_creator_id: room.creatorId\n      )\n    end)\n  end\n\n  # TODO: bake this into the supervision tree itself by having the\n  # supervisor fetch and look up the the list of online voice servers\n  # by querying GCP tags.\n  defp start_rabbits() do\n    n = Application.get_env(:kousa, :num_voice_servers, 1) - 1\n\n    IO.puts(\"about to start_rabbits\")\n\n    0..n\n    |> Enum.map(&Kousa.Utils.VoiceServerUtils.idx_to_str_id/1)\n    |> Enum.each(fn id ->\n      Onion.VoiceRabbit.start_supervised(id)\n      Onion.VoiceOnlineRabbit.start_supervised(id)\n    end)\n\n    IO.puts(\"finished rabbits\")\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/bot_auth_rate_limit.ex",
    "content": "defmodule Onion.BotAuthRateLimit do\n  use GenServer\n  def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)\n\n  @sweep_after :timer.seconds(60 * 60 * 24)\n\n  @spec init(any) :: :ignore | {:ok, nil, :hibernate}\n  def init(_) do\n    :ets.new(__MODULE__, [:set, :public, :named_table])\n    schedule_sweep()\n\n    {:ok, nil, :hibernate}\n  catch\n    _, :badarg ->\n      :ignore\n  end\n\n  def handle_info(:sweep, state) do\n    :ets.delete_all_objects(__MODULE__)\n    schedule_sweep()\n    {:noreply, state}\n  end\n\n  defp schedule_sweep do\n    Process.send_after(self(), :sweep, @sweep_after)\n  end\n\n  def get(key) do\n    case :ets.lookup(__MODULE__, key) do\n      [{^key, value}] -> value\n      [] -> nil\n    end\n  end\n\n  # note it shouldn't be called \"put\" because that implies\n  # returning back the same structure, this is a mutable\n  # operation\n  def set(key, value) do\n    :ets.insert(__MODULE__, {key, value})\n    :ok\n  end\n\n  def delete(key) do\n    :ets.delete(__MODULE__, key)\n    :ok\n  end\n\n  def reset do\n    __MODULE__\n    |> Process.whereis()\n    |> Process.exit(:kill)\n  end\n\n  # atomic updates.  Consider guarding keys to be privileged\n  # values, that are set up with default values in init/1\n  def update_counter(key, increment, default_value) do\n    :ets.update_counter(__MODULE__, key, increment, default_value)\n    :ok\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/chat.ex",
    "content": "defmodule Onion.Chat do\n  use GenServer, restart: :temporary\n\n  alias Onion.PubSub\n  alias Broth.Message.Chat.Delete\n  alias Broth.Message.Chat.Send\n  alias Kousa.Utils.UUID\n\n  require Logger\n\n  defstruct room_id: \"\",\n            room_creator_id: \"\",\n            users: [],\n            ban_map: %{},\n            last_message_map: %{},\n            follow_at_map: %{},\n            chat_mode: :default,\n            chat_throttle: 1000\n\n  @type state :: %__MODULE__{\n          room_id: String.t(),\n          room_creator_id: String.t(),\n          users: [String.t()],\n          ban_map: map(),\n          last_message_map: %{optional(UUID.t()) => DateTime.t()},\n          follow_at_map: %{optional(UUID.t()) => DateTime.t() | nil},\n          chat_mode: Beef.Schemas.Room.chatMode(),\n          chat_throttle: integer()\n        }\n\n  #################################################################################\n  # REGISTRY AND SUPERVISION BOILERPLATE\n\n  defp via(user_id), do: {:via, Registry, {Onion.RoomChatRegistry, user_id}}\n\n  defp cast(user_id, params), do: GenServer.cast(via(user_id), params)\n  defp call(user_id, params), do: GenServer.call(via(user_id), params)\n\n  def start_link_supervised(initial_values) do\n    callers = [self() | Process.get(:\"$callers\", [])]\n\n    case DynamicSupervisor.start_child(\n           Onion.RoomChatDynamicSupervisor,\n           {__MODULE__, Keyword.merge(initial_values, callers: callers)}\n         ) do\n      {:ok, pid} ->\n        # ensures that the chat dies alongside the room\n        Process.link(pid)\n        {:ok, pid}\n\n      {:error, {:already_started, pid}} ->\n        Logger.warn(\n          \"unexpectedly tried to restart already started Room chat #{initial_values[:room_id]}\"\n        )\n\n        Process.link(pid)\n        {:ignored, pid}\n\n      error ->\n        error\n    end\n  end\n\n  def child_spec(init), do: %{super(init) | id: Keyword.get(init, :room_id)}\n\n  def count, do: Registry.count(Onion.RoomChatRegistry)\n\n  ###############################################################################\n  ## INITIALIZATION BOILERPLATE\n\n  def start_link(init) do\n    GenServer.start_link(__MODULE__, init, name: via(init[:room_id]))\n  end\n\n  def init(init) do\n    # adopt callers from the call point.\n    Process.put(:\"$callers\", init[:callers])\n    {:ok, struct(__MODULE__, init)}\n  end\n\n  def kill(room_id) do\n    Onion.RoomChatRegistry\n    |> Registry.lookup(room_id)\n    |> Enum.each(fn {room_pid, _} ->\n      Process.exit(room_pid, :kill)\n    end)\n  end\n\n  def ws_fan(users, msg) do\n    Enum.each(users, fn uid ->\n      Onion.UserSession.send_ws(uid, nil, msg)\n    end)\n  end\n\n  ######################################################################\n  ## API\n\n  def set_room_creator_id(room_id, id) do\n    cast(room_id, {:set_room_creator_id, id})\n  end\n\n  defp set_room_creator_id_impl(id, %__MODULE__{} = state) do\n    {:noreply, %{state | room_creator_id: id, follow_at_map: %{}}}\n  end\n\n  def set_chat_throttle(room_id, value) do\n    cast(room_id, {:set_chat_throttle, value})\n  end\n\n  defp set_chat_throttle_impl(value, %__MODULE__{} = state) do\n    {:noreply, %{state | chat_throttle: value}}\n  end\n\n  def banned?(room_id, who), do: call(room_id, {:banned?, who})\n\n  defp banned_impl(who, _reply, state) do\n    {:reply, user_banned?(who, state), state}\n  end\n\n  defp user_banned?(who, state) do\n    who in Map.keys(state.ban_map)\n  end\n\n  alias Beef.Follows\n  alias Beef.Schemas.Follow\n\n  # users that meet the following criteria,\n  # can chat in follower_only mode:\n  # 0. creator of room\n  # 1. mod\n  # 2. speaker\n  # 3. following room owner prior to joining room\n\n  defp eligible_to_chat?(\n         from,\n         %__MODULE__{\n           chat_mode: :follower_only,\n           room_creator_id: room_creator_id\n         } = state\n       )\n       when from == room_creator_id,\n       do: {state, true}\n\n  alias Beef.Rooms\n\n  defp eligible_to_chat?(\n         from,\n         %__MODULE__{\n           chat_mode: :follower_only,\n           room_creator_id: room_creator_id,\n           follow_at_map: follow_at_map\n         } = state\n       ) do\n    {new_state, inserted_at_or_true} =\n      if Map.has_key?(follow_at_map, from) do\n        # cache hit\n        {state, Map.get(follow_at_map, from)}\n      else\n        {status, _} = Rooms.get_room_status(from)\n\n        # cache miss\n        inserted_at_or_true =\n          if status in [:mod, :speaker] do\n            true\n          else\n            case Follows.get_follow(room_creator_id, from) do\n              nil ->\n                nil\n\n              %Follow{inserted_at: inserted_at} ->\n                inserted_at\n            end\n          end\n\n        {%__MODULE__{state | follow_at_map: Map.put(follow_at_map, from, inserted_at_or_true)},\n         inserted_at_or_true}\n      end\n\n    {new_state, not is_nil(inserted_at_or_true)}\n  end\n\n  defp eligible_to_chat?(_, state), do: {state, true}\n\n  def unban_user(room_id, user_id), do: cast(room_id, {:unban_user, user_id})\n\n  def remove_user(room_id, user_id), do: cast(room_id, {:remove_user, user_id})\n\n  defp remove_user_impl(user_id, state) do\n    {:noreply, %{state | users: Enum.reject(state.users, &(&1 == user_id))}}\n  end\n\n  def add_user(room_id, user_id), do: cast(room_id, {:add_user, user_id})\n\n  defp add_user_impl(user_id, state) do\n    if user_id in state.users do\n      {:noreply, state}\n    else\n      {:noreply, %{state | users: [user_id | state.users]}}\n    end\n  end\n\n  def ban_user(room_id, user_id), do: cast(room_id, {:ban_user, user_id})\n\n  defp ban_user_impl(user_id, state) do\n    ws_fan(state.users, %{\n      op: \"chat_user_banned\",\n      d: %{\n        userId: user_id\n      }\n    })\n\n    {:noreply, %{state | ban_map: Map.put(state.ban_map, user_id, 1)}}\n  end\n\n  defp unban_user_impl(user_id, state) do\n    ws_fan(state.users, %{\n      op: \"chat_user_unbanned\",\n      d: %{\n        userId: user_id\n      }\n    })\n\n    {:noreply, %{state | ban_map: Map.delete(state.ban_map, user_id)}}\n  end\n\n  def set_can_chat(room_id, user_id), do: cast(room_id, {:set_can_chat, user_id})\n\n  defp set_can_chat_impl(user_id, %__MODULE__{follow_at_map: follow_at_map} = state) do\n    {:noreply, %{state | follow_at_map: Map.put(follow_at_map, user_id, true)}}\n  end\n\n  def set(room_id, key, value), do: cast(room_id, {:set, key, value})\n\n  defp set_impl(key, value, state) do\n    {:noreply, Map.put(state, key, value)}\n  end\n\n  def get(room_id, key), do: call(room_id, {:get, key})\n\n  defp get_impl(key, _reply, state) do\n    {:reply, Map.get(state, key), state}\n  end\n\n  #####################################################################\n  ## send message\n\n  @spec send_msg(UUID.t(), Send.t()) :: :ok\n  def send_msg(room_id, payload) do\n    cast(room_id, {:send_msg, payload})\n  end\n\n  defp send_msg_impl(_, %__MODULE__{chat_mode: :disabled} = state) do\n    {:noreply, state}\n  end\n\n  @spec send_msg_impl(Send.t(), state) :: {:noreply, state}\n  defp send_msg_impl(payload = %{from: from}, %__MODULE__{} = state) do\n    # throttle sender\n    with false <- should_throttle?(from, state),\n         false <- user_banned?(from, state) do\n      {new_state, can_chat} = eligible_to_chat?(from, state)\n\n      if can_chat do\n        dispatch_message(payload, new_state)\n\n        {:noreply,\n         %{\n           new_state\n           | last_message_map: Map.put(new_state.last_message_map, from, DateTime.utc_now())\n         }}\n      else\n        {:noreply, new_state}\n      end\n    else\n      _ -> {:noreply, state}\n    end\n  end\n\n  @spec should_throttle?(UUID.t(), state) :: boolean\n  defp should_throttle?(user_id, %__MODULE__{last_message_map: m, chat_throttle: ct})\n       when is_map_key(m, user_id) do\n    DateTime.diff(DateTime.utc_now(), m[user_id], :millisecond) <\n      ct\n  end\n\n  defp should_throttle?(_, _), do: false\n\n  defp dispatch_message(payload, state) do\n    case payload.whisperedTo do\n      [] ->\n        PubSub.broadcast(\"chat:\" <> state.room_id, %Broth.Message{\n          operator: \"chat:send\",\n          payload: payload\n        })\n\n        :ok\n\n      list ->\n        # I am doing user blocking at socket_handler level\n        list\n        |> List.insert_at(0, payload.from)\n        |> Enum.uniq\n        |> Enum.each(fn recipient_id ->\n          PubSub.broadcast(\"chat:\" <> recipient_id, %Broth.Message{\n            operator: \"chat:send\",\n            payload: payload\n          })\n        end)\n    end\n  end\n\n  #############################################################################\n  ## delete message\n\n  # it seems like this doesn't need to be here, but, we are sending this\n  # through the chat room for two reasons.\n  # - because the chat room genserver is single-threaded it will make sure that\n  #   the messages are well-ordered for everyone.\n  # - eventually when the chat message logs are taken, we'll be able to\n  #   add soft-deletion of messages into the message logs.\n  @spec delete_message(UUID.t(), Delete.t()) :: :ok\n  def delete_message(room_id, deletion = %Delete{}) do\n    cast(room_id, {:delete_message, deletion})\n  end\n\n  defp delete_message_impl(deletion, state) do\n    PubSub.broadcast(\"chat:\" <> state.room_id, %Broth.Message{\n      operator: \"chat:delete\",\n      payload: deletion\n    })\n\n    {:noreply, state}\n  end\n\n  #############################################################################\n  ## ROUTER\n\n  def handle_call({:banned?, who}, reply, state), do: banned_impl(who, reply, state)\n\n  def handle_call({:get, key}, reply, state), do: get_impl(key, reply, state)\n\n  def handle_cast({:set_can_chat, user_id}, state) do\n    set_can_chat_impl(user_id, state)\n  end\n\n  def handle_cast({:set_room_creator_id, id}, state) do\n    set_room_creator_id_impl(id, state)\n  end\n\n  def handle_cast({:set_chat_throttle, value}, state) do\n    set_chat_throttle_impl(value, state)\n  end\n\n  def handle_cast({:set, key, value}, state), do: set_impl(key, value, state)\n\n  def handle_cast({:unban_user, user_id}, state), do: unban_user_impl(user_id, state)\n\n  def handle_cast({:remove_user, user_id}, state), do: remove_user_impl(user_id, state)\n\n  def handle_cast({:add_user, user_id}, state), do: add_user_impl(user_id, state)\n\n  def handle_cast({:send_msg, message}, state), do: send_msg_impl(message, state)\n\n  def handle_cast({:delete_message, payload}, state), do: delete_message_impl(payload, state)\n\n  def handle_cast({:ban_user, user_id}, state), do: ban_user_impl(user_id, state)\nend\n"
  },
  {
    "path": "kousa/lib/onion/pub_sub.ex",
    "content": "defmodule Onion.PubSub do\n  @moduledoc \"\"\"\n  an opinionated wrapper around Phoenix.PubSub.\n\n  - Each message is tagged with the topic string, to help you sort\n    messages coming down the pipe.\n\n  - Messages are required to be structs of the type `Broth.Message`\n  \"\"\"\n\n  alias Phoenix.PubSub\n\n  @valid_classes ~w(chat user)\n\n  def subscribe(topic = <<class::binary-size(4), ?:>> <> _) when class in @valid_classes do\n    PubSub.subscribe(__MODULE__, topic)\n  end\n\n  def broadcast(\n        topic = <<class::binary-size(4), ?:>> <> _,\n        message = %_{}\n      )\n      when class in @valid_classes do\n    # do other validation here in test and dev, in the future.\n\n    PubSub.broadcast(__MODULE__, topic, {topic, message})\n  end\n\n  def unsubscribe(topic = <<class::binary-size(4), ?:>> <> _) when class in @valid_classes do\n    PubSub.unsubscribe(__MODULE__, topic)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/room_session.ex",
    "content": "defmodule Onion.RoomSession do\n  use GenServer, restart: :temporary\n\n  # TODO: change this.  Actually, make it an ecto thing.\n  defmodule State do\n    @type t :: %__MODULE__{\n            room_id: String.t(),\n            room_creator_id: String.t(),\n            voice_server_id: String.t(),\n            users: [String.t()],\n            muteMap: map(),\n            deafMap: map(),\n            inviteMap: map(),\n            activeSpeakerMap: map(),\n            auto_speaker: boolean()\n          }\n\n    defstruct room_id: \"\",\n              room_creator_id: \"\",\n              voice_server_id: \"\",\n              users: [],\n              muteMap: %{},\n              deafMap: %{},\n              inviteMap: %{},\n              activeSpeakerMap: %{},\n              auto_speaker: false\n  end\n\n  #################################################################################\n  # REGISTRY AND SUPERVISION BOILERPLATE\n\n  defp via(user_id), do: {:via, Registry, {Onion.RoomSessionRegistry, user_id}}\n\n  defp cast(user_id, params), do: GenServer.cast(via(user_id), params)\n  defp call(user_id, params), do: GenServer.call(via(user_id), params)\n\n  def start_supervised(initial_values) do\n    callers = [self() | Process.get(:\"$callers\", [])]\n\n    case DynamicSupervisor.start_child(\n           Onion.RoomSessionDynamicSupervisor,\n           {__MODULE__, Keyword.merge(initial_values, callers: callers)}\n         ) do\n      {:error, {:already_started, pid}} -> {:ignored, pid}\n      error -> error\n    end\n  end\n\n  def child_spec(init), do: %{super(init) | id: Keyword.get(init, :room_id)}\n\n  def count, do: Registry.count(Onion.RoomSessionRegistry)\n  def lookup(room_id), do: Registry.lookup(Onion.RoomSessionRegistry, room_id)\n\n  ###############################################################################\n  ## INITIALIZATION BOILERPLATE\n\n  def start_link(init) do\n    GenServer.start_link(__MODULE__, init, name: via(init[:room_id]))\n  end\n\n  def init(init) do\n    # adopt callers from the call point.\n    Process.put(:\"$callers\", init[:callers])\n\n    # also launch a linked, supervised room.\n    Onion.Chat.start_link_supervised(init)\n    {:ok, struct(State, init)}\n  end\n\n  ########################################################################\n  ## API\n\n  def ws_fan(users, msg) do\n    Enum.each(users, fn uid ->\n      Onion.UserSession.send_ws(uid, nil, msg)\n    end)\n  end\n\n  def get(room_id, key), do: call(room_id, {:get, key})\n\n  defp get_impl(key, _reply, state) do\n    {:reply, Map.get(state, key), state}\n  end\n\n  def get_maps(room_id), do: call(room_id, :get_maps)\n\n  defp get_maps_impl(_reply, state) do\n    {:reply, {state.muteMap, state.deafMap, state.auto_speaker, state.activeSpeakerMap}, state}\n  end\n\n  def set(user_id, key, value), do: cast(user_id, {:set, key, value})\n\n  defp set_impl(key, value, state) do\n    {:noreply, Map.put(state, key, value)}\n  end\n\n  def redeem_invite(room_id, user_id), do: call(room_id, {:redeem_invite, user_id})\n\n  defp redeem_invite_impl(user_id, _reply, state) do\n    reply = if Map.has_key?(state.inviteMap, user_id), do: :ok, else: :error\n\n    {:reply, reply, %{state | inviteMap: Map.delete(state.inviteMap, user_id)}}\n  end\n\n  def speaking_change(room_id, user_id, value) do\n    cast(room_id, {:speaking_change, user_id, value})\n  end\n\n  defp speaking_change_impl(user_id, value, state) when is_boolean(value) do\n    muteMap = if value, do: Map.delete(state.muteMap, user_id), else: state.muteMap\n    deafMap = if value, do: Map.delete(state.deafMap, user_id), else: state.deafMap\n\n    newActiveSpeakerMap =\n      if value,\n        do: Map.put(state.activeSpeakerMap, user_id, true),\n        else: Map.delete(state.activeSpeakerMap, user_id)\n\n    ws_fan(state.users, %{\n      op: \"active_speaker_change\",\n      d: %{\n        activeSpeakerMap: newActiveSpeakerMap,\n        roomId: state.room_id,\n        muteMap: muteMap,\n        deafMap: deafMap\n      }\n    })\n\n    {:noreply, %{state | activeSpeakerMap: newActiveSpeakerMap}}\n  end\n\n  def set_room_creator_id(room_id, id) do\n    cast(room_id, {:set_room_creator_id, id})\n  end\n\n  defp set_room_creator_id_impl(id, %State{} = state) do\n    Onion.Chat.set_room_creator_id(state.room_id, id)\n    {:noreply, %{state | room_creator_id: id}}\n  end\n\n  def set_auto_speaker(room_id, value) when is_boolean(value) do\n    cast(room_id, {:set_auto_speaker, value})\n  end\n\n  defp set_auto_speaker_impl(value, state) do\n    {:noreply, %{state | auto_speaker: value}}\n  end\n\n  def broadcast_ws(room_id, msg), do: cast(room_id, {:broadcast_ws, msg})\n\n  defp broadcast_ws_impl(msg, state) do\n    ws_fan(state.users, msg)\n    {:noreply, state}\n  end\n\n  def create_invite(room_id, user_id, user_info) do\n    cast(room_id, {:create_invite, user_id, user_info})\n  end\n\n  defp create_invite_impl(user_id, user_info, state) do\n    Onion.UserSession.send_ws(\n      user_id,\n      nil,\n      %{\n        op: \"invitation_to_room\",\n        d:\n          Map.merge(\n            %{roomId: state.room_id},\n            user_info\n          )\n      }\n    )\n\n    {:noreply,\n     %{\n       state\n       | inviteMap: Map.put(state.inviteMap, user_id, true)\n     }}\n  end\n\n  def remove_speaker(room_id, user_id), do: cast(room_id, {:remove_speaker, user_id})\n\n  defp remove_speaker_impl(user_id, state) do\n    new_mm = Map.delete(state.muteMap, user_id)\n    new_dm = Map.delete(state.deafMap, user_id)\n\n    Onion.VoiceRabbit.send(state.voice_server_id, %{\n      op: \"remove-speaker\",\n      d: %{roomId: state.room_id, peerId: user_id},\n      uid: user_id\n    })\n\n    ws_fan(state.users, %{\n      op: \"speaker_removed\",\n      d: %{\n        userId: user_id,\n        roomId: state.room_id,\n        muteMap: new_mm,\n        deafMap: new_dm,\n        raiseHandMap: %{}\n      }\n    })\n\n    {:noreply, %State{state | muteMap: new_mm, deafMap: new_dm}}\n  end\n\n  def add_speaker(room_id, user_id, muted?, deafened?)\n      when is_boolean(muted?) and is_boolean(deafened?) do\n    cast(room_id, {:add_speaker, user_id, muted?, deafened?})\n  end\n\n  def add_speaker_impl(user_id, muted?, deafened?, state) do\n    new_mm =\n      if muted?,\n        do: Map.put(state.muteMap, user_id, true),\n        else: Map.delete(state.muteMap, user_id)\n\n    new_dm =\n      if(deafened?,\n        do: Map.put(state.deafMap, user_id, true),\n        else: Map.delete(state.deafMap, user_id)\n      )\n\n    Onion.VoiceRabbit.send(state.voice_server_id, %{\n      op: \"add-speaker\",\n      d: %{roomId: state.room_id, peerId: user_id},\n      uid: user_id\n    })\n\n    ws_fan(state.users, %{\n      op: \"speaker_added\",\n      d: %{\n        userId: user_id,\n        roomId: state.room_id,\n        muteMap: new_mm,\n        deafMap: new_dm\n      }\n    })\n\n    {:noreply, %{state | muteMap: new_mm, deafMap: new_dm}}\n  end\n\n  def join_room(room_id, user_id, mute, deaf, opts \\\\ []) do\n    cast(room_id, {:join_room, user_id, mute, deaf, opts})\n  end\n\n  defp join_room_impl(user_id, mute, deaf, opts, state) do\n    Onion.Chat.add_user(state.room_id, user_id)\n\n    # consider using MapSet instead!!\n    muteMap =\n      case mute do\n        nil -> state.muteMap\n        true -> Map.put(state.muteMap, user_id, true)\n        false -> Map.delete(state.muteMap, user_id)\n      end\n\n    deafMap =\n      case deaf do\n        nil -> state.deafMap\n        true -> Map.put(state.deafMap, user_id, true)\n        false -> Map.delete(state.deafMap, user_id)\n      end\n\n    unless opts[:no_fan] do\n      ws_fan(state.users, %{\n        op: \"new_user_join_room\",\n        d: %{\n          user: Beef.Users.get_by_id_with_room_permissions(user_id),\n          muteMap: muteMap,\n          deafMap: deafMap,\n          roomId: state.room_id\n        }\n      })\n    end\n\n    {:noreply,\n     %{\n       state\n       | users: [\n           # maybe use a set\n           user_id\n           | Enum.filter(state.users, fn uid -> uid != user_id end)\n         ],\n         muteMap: muteMap,\n         deafMap: deafMap\n     }}\n  end\n\n  def mute(room_id, user_id, value), do: cast(room_id, {:mute, user_id, value})\n\n  defp mute_impl(user_id, value, state) do\n    changed = value != Map.has_key?(state.muteMap, user_id)\n\n    if changed do\n      ws_fan(Enum.filter(state.users, &(&1 != user_id)), %{\n        op: \"mute_changed\",\n        d: %{userId: user_id, value: value, roomId: state.room_id}\n      })\n    end\n\n    {:noreply,\n     %{\n       state\n       | muteMap:\n           if(not value,\n             do: Map.delete(state.muteMap, user_id),\n             else: Map.put(state.muteMap, user_id, true)\n           ),\n         activeSpeakerMap:\n           if(value, do: Map.delete(state.activeSpeakerMap, user_id), else: state.activeSpeakerMap),\n         deafMap: if(value, do: Map.delete(state.deafMap, user_id), else: state.deafMap)\n     }}\n  end\n\n  def deafen(room_id, user_id, value), do: cast(room_id, {:deafen, user_id, value})\n\n  defp deafen_impl(user_id, value, state) do\n    changed = value != Map.has_key?(state.deafMap, user_id)\n\n    if changed do\n      ws_fan(Enum.filter(state.users, &(&1 != user_id)), %{\n        op: \"deafen_changed\",\n        d: %{userId: user_id, value: value, roomId: state.room_id}\n      })\n    end\n\n    {:noreply,\n     %{\n       state\n       | deafMap:\n           if(not value,\n             do: Map.delete(state.deafMap, user_id),\n             else: Map.put(state.deafMap, user_id, true)\n           ),\n         activeSpeakerMap:\n           if(value, do: Map.delete(state.activeSpeakerMap, user_id), else: state.activeSpeakerMap),\n         muteMap: if(value, do: Map.delete(state.muteMap, user_id), else: state.muteMap)\n     }}\n  end\n\n  def destroy(room_id, user_id), do: cast(room_id, {:destroy, user_id})\n\n  defp destroy_impl(user_id, state) do\n    users = Enum.filter(state.users, fn uid -> uid != user_id end)\n\n    ws_fan(users, %{\n      op: \"room_destroyed\",\n      d: %{roomId: state.room_id}\n    })\n\n    {:stop, :normal, state}\n  end\n\n  def kick_from_room(room_id, user_id), do: cast(room_id, {:kick_from_room, user_id})\n\n  defp kick_from_room_impl(user_id, state) do\n    users = Enum.filter(state.users, fn uid -> uid != user_id end)\n\n    Onion.Chat.remove_user(state.room_id, user_id)\n\n    Onion.VoiceRabbit.send(state.voice_server_id, %{\n      op: \"close-peer\",\n      uid: user_id,\n      d: %{peerId: user_id, roomId: state.room_id, kicked: true}\n    })\n\n    ws_fan(users, %{\n      op: \"user_left_room\",\n      d: %{userId: user_id, roomId: state.room_id, kicked: true}\n    })\n\n    {:noreply,\n     %{\n       state\n       | users: users,\n         muteMap: Map.delete(state.muteMap, user_id),\n         deafMap: Map.delete(state.deafMap, user_id)\n     }}\n  end\n\n  def leave_room(room_id, user_id), do: cast(room_id, {:leave_room, user_id})\n\n  defp leave_room_impl(user_id, state) do\n    users = Enum.reject(state.users, &(&1 == user_id))\n\n    Onion.Chat.remove_user(state.room_id, user_id)\n\n    Onion.VoiceRabbit.send(state.voice_server_id, %{\n      op: \"close-peer\",\n      uid: user_id,\n      d: %{peerId: user_id, roomId: state.room_id}\n    })\n\n    ws_fan(users, %{\n      op: \"user_left_room\",\n      d: %{userId: user_id, roomId: state.room_id}\n    })\n\n    new_state = %{\n      state\n      | users: users,\n        muteMap: Map.delete(state.muteMap, user_id),\n        deafMap: Map.delete(state.deafMap, user_id)\n    }\n\n    # terminate room if it's empty\n    case new_state.users do\n      [] ->\n        {:stop, :normal, new_state}\n\n      _ ->\n        {:noreply, new_state}\n    end\n  end\n\n  ########################################################################\n  ## ROUTER\n\n  def handle_call({:get, key}, reply, state), do: get_impl(key, reply, state)\n\n  def handle_call(:get_maps, reply, state), do: get_maps_impl(reply, state)\n\n  def handle_call({:redeem_invite, user_id}, reply, state) do\n    redeem_invite_impl(user_id, reply, state)\n  end\n\n  def handle_cast({:set, key, value}, state), do: set_impl(key, value, state)\n\n  def handle_cast({:kick_from_room, user_id}, state) do\n    kick_from_room_impl(user_id, state)\n  end\n\n  def handle_cast({:speaking_change, user_id, value}, state) do\n    speaking_change_impl(user_id, value, state)\n  end\n\n  def handle_cast({:set_room_creator_id, id}, state) do\n    set_room_creator_id_impl(id, state)\n  end\n\n  def handle_cast({:set_auto_speaker, value}, state) do\n    set_auto_speaker_impl(value, state)\n  end\n\n  def handle_cast({:broadcast_ws, msg}, state) do\n    broadcast_ws_impl(msg, state)\n  end\n\n  def handle_cast({:create_invite, user_id, user_info}, state) do\n    create_invite_impl(user_id, user_info, state)\n  end\n\n  def handle_cast({:remove_speaker, user_id}, state) do\n    remove_speaker_impl(user_id, state)\n  end\n\n  def handle_cast({:add_speaker, user_id, muted?, deafened?}, state) do\n    add_speaker_impl(user_id, muted?, deafened?, state)\n  end\n\n  def handle_cast({:join_room, user_id, mute, deaf, opts}, state) do\n    join_room_impl(user_id, mute, deaf, opts, state)\n  end\n\n  def handle_cast({:mute, user_id, value}, state) do\n    mute_impl(user_id, value, state)\n  end\n\n  def handle_cast({:deafen, user_id, value}, state) do\n    deafen_impl(user_id, value, state)\n  end\n\n  def handle_cast({:destroy, user_id}, state) do\n    destroy_impl(user_id, state)\n  end\n\n  def handle_cast({:leave_room, user_id}, state) do\n    leave_room_impl(user_id, state)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/stats_cache.ex",
    "content": "defmodule Onion.StatsCache do\n  use GenServer\n  def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)\n\n  @spec init(any) :: :ignore | {:ok, nil, :hibernate}\n  def init(_) do\n    :ets.new(__MODULE__, [:set, :public, :named_table])\n\n    {:ok, nil, :hibernate}\n  catch\n    _, :badarg ->\n      :ignore\n  end\n\n  def get(key) do\n    case :ets.lookup(__MODULE__, key) do\n      [{^key, value}] -> value\n      [] -> nil\n    end\n  end\n\n  # note it shouldn't be called \"put\" because that implies\n  # returning back the same structure, this is a mutable\n  # operation\n  def set(key, value) do\n    :ets.insert(__MODULE__, {key, value})\n    :ok\n  end\n\n  def delete(key) do\n    :ets.delete(__MODULE__, key)\n    :ok\n  end\n\n  def reset do\n    __MODULE__\n    |> Process.whereis()\n    |> Process.exit(:kill)\n  end\n\n  # atomic updates.  Consider guarding keys to be privileged\n  # values, that are set up with default values in init/1\n  def update_counter(key, increment) do\n    :ets.update_counter(__MODULE__, key, increment)\n    :ok\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/supervisors/room_chat.ex",
    "content": "defmodule Onion.Supervisors.Chat do\n  use Supervisor\n\n  def start_link(init_arg) do\n    Supervisor.start_link(__MODULE__, init_arg)\n  end\n\n  @impl true\n  def init(_init_arg) do\n    children = [\n      {Registry, keys: :unique, name: Onion.RoomChatRegistry},\n      {DynamicSupervisor, name: Onion.RoomChatDynamicSupervisor, strategy: :one_for_one}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/supervisors/room_session.ex",
    "content": "defmodule Onion.Supervisors.RoomSession do\n  use Supervisor\n\n  def start_link(init_arg) do\n    Supervisor.start_link(__MODULE__, init_arg)\n  end\n\n  @impl true\n  def init(_init_arg) do\n    children = [\n      {Registry, keys: :unique, name: Onion.RoomSessionRegistry},\n      {DynamicSupervisor, name: Onion.RoomSessionDynamicSupervisor, strategy: :one_for_one}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/supervisors/user_session.ex",
    "content": "defmodule Onion.Supervisors.UserSession do\n  use Supervisor\n\n  def start_link(init_arg) do\n    Supervisor.start_link(__MODULE__, init_arg)\n  end\n\n  @impl true\n  def init(_init_arg) do\n    children = [\n      {Registry, keys: :unique, name: Onion.UserSessionRegistry},\n      {DynamicSupervisor, name: Onion.UserSessionDynamicSupervisor, strategy: :one_for_one}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/supervisors/voice_online_rabbit.ex",
    "content": "defmodule Onion.Supervisors.VoiceOnlineRabbit do\n  use Supervisor\n\n  def start_link(init_arg) do\n    Supervisor.start_link(__MODULE__, init_arg)\n  end\n\n  @impl true\n  def init(_init_arg) do\n    children = [\n      {Registry, keys: :unique, name: Onion.VoiceOnlineRabbitRegistry},\n      {DynamicSupervisor, name: Onion.VoiceOnlineRabbitDynamicSupervisor, strategy: :one_for_one}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/supervisors/voice_rabbit.ex",
    "content": "defmodule Onion.Supervisors.VoiceRabbit do\n  use Supervisor\n\n  def start_link(init_arg) do\n    Supervisor.start_link(__MODULE__, init_arg)\n  end\n\n  @impl true\n  def init(_init_arg) do\n    children = [\n      {Registry, keys: :unique, name: Onion.VoiceRabbitRegistry},\n      {DynamicSupervisor, name: Onion.VoiceRabbitDynamicSupervisor, strategy: :one_for_one}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/telemetry.ex",
    "content": "defmodule Onion.Telemetry do\n  use GenServer\n\n  def start_link(_) do\n    GenServer.start_link(__MODULE__, [], name: __MODULE__)\n  end\n\n  def init(_opts) do\n    :timer.send_interval(10_000, self(), :collect_metrics)\n    {:ok, %{}}\n  end\n\n  def handle_info(:collect_metrics, state) do\n    Kousa.Metric.UserSessions.set(Onion.UserSession.count())\n    {:noreply, state}\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/user_session.ex",
    "content": "defmodule Onion.UserSession do\n  use GenServer, restart: :temporary\n  alias Beef.Rooms\n\n  # TODO: change this\n  defmodule State do\n    @type t :: %__MODULE__{\n            user_id: String.t(),\n            avatar_url: String.t(),\n            banner_url: String.t(),\n            username: String.t(),\n            display_name: String.t(),\n            current_room_id: String.t(),\n            bot_owner_id: String.t(),\n            muted: boolean(),\n            deafened: boolean(),\n            ip: String.t(),\n            pid: pid()\n          }\n\n    defstruct user_id: nil,\n              current_room_id: nil,\n              muted: false,\n              ip: nil,\n              deafened: false,\n              pid: nil,\n              username: nil,\n              bot_owner_id: nil,\n              display_name: nil,\n              avatar_url: nil,\n              banner_url: nil\n  end\n\n  #################################################################################\n  # REGISTRY AND SUPERVISION BOILERPLATE\n\n  defp via(user_id), do: {:via, Registry, {Onion.UserSessionRegistry, user_id}}\n\n  defp cast(user_id, params), do: GenServer.cast(via(user_id), params)\n  defp call(user_id, params), do: GenServer.call(via(user_id), params)\n\n  def start_supervised(initial_values) do\n    callers = [self() | Process.get(:\"$callers\", [])]\n\n    case DynamicSupervisor.start_child(\n           Onion.UserSessionDynamicSupervisor,\n           {__MODULE__, Keyword.merge(initial_values, callers: callers)}\n         ) do\n      {:error, {:already_started, pid}} -> {:ignored, pid}\n      error -> error\n    end\n  end\n\n  def child_spec(init), do: %{super(init) | id: Keyword.get(init, :user_id)}\n\n  def count, do: Registry.count(Onion.UserSessionRegistry)\n\n  def lookup(user_id), do: Registry.lookup(Onion.UserSessionRegistry, user_id)\n\n  ###############################################################################\n  ## INITIALIZATION BOILERPLATE\n\n  def start_link(init) do\n    GenServer.start_link(__MODULE__, init, name: via(init[:user_id]))\n  end\n\n  def init(init) do\n    # transfer callers into the running process.\n    Process.put(:\"$callers\", Keyword.get(init, :callers))\n    {:ok, struct(State, init)}\n  end\n\n  ##############################################################################\n  ## API HOOKS\n  ## TODO: CHANGE CASTS TO CALLS\n\n  def set(user_id, key, value), do: cast(user_id, {:set, key, value})\n\n  defp set_impl(key, value, state) do\n    {:noreply, Map.put(state, key, value)}\n  end\n\n  def send_ws(user_id, platform, msg), do: cast(user_id, {:send_ws, platform, msg})\n\n  defp send_ws_impl(_platform, msg, state = %{pid: pid}) do\n    # TODO: refactor this to not use ws-datastructures\n    if pid, do: Broth.SocketHandler.remote_send(pid, msg)\n    {:noreply, state}\n  end\n\n  def set_mute(user_id, value) when is_boolean(value),\n    do: cast(user_id, {:set_mute, value})\n\n  defp set_mute_impl(value, state = %{current_room_id: current_room_id}) do\n    if current_room_id do\n      Onion.RoomSession.mute(current_room_id, state.user_id, value)\n    end\n\n    {:noreply, %{state | muted: value}}\n  end\n\n  def set_deafen(user_id, value) when is_boolean(value),\n    do: cast(user_id, {:set_deafen, value})\n\n  defp set_deafen_impl(value, state = %{current_room_id: current_room_id}) do\n    if current_room_id do\n      Onion.RoomSession.deafen(current_room_id, state.user_id, value)\n    end\n\n    {:noreply, %{state | deafened: value}}\n  end\n\n  def new_tokens(user_id, tokens), do: cast(user_id, {:new_tokens, tokens})\n\n  defp new_tokens_impl(tokens, state = %{pid: pid}) do\n    # TODO: refactor this to not use ws-datastructures\n    if pid, do: Broth.SocketHandler.remote_send(pid, %{op: \"new-tokens\", d: tokens})\n    {:noreply, state}\n  end\n\n  def set_state(user_id, info), do: cast(user_id, {:set_state, info})\n\n  defp set_state_impl(info, state) do\n    {:noreply, Map.merge(state, info)}\n  end\n\n  def set_current_room_id(user_id, current_room_id) do\n    set_state(user_id, %{current_room_id: current_room_id})\n  end\n\n  def get_info_for_msg(user_id), do: call(user_id, :get_info_for_msg)\n\n  defp get_info_for_msg_impl(_reply, state) do\n    {:reply, {state.avatar_url, state.display_name, state.username}, state}\n  end\n\n  def get_current_room_id(user_id) do\n    get(user_id, :current_room_id)\n  end\n\n  def get(user_id, key), do: call(user_id, {:get, key})\n\n  defp get_impl(key, _reply, state) do\n    {:reply, Map.get(state, key), state}\n  end\n\n  # temporary function that exists so that each user can only have\n  # one tenant websocket.\n  def set_active_ws(user_id, pid), do: call(user_id, {:set_active_ws, pid})\n\n  defp set_active_ws(pid, _reply, state) do\n    if state.pid do\n      # terminates another websocket that happened to have been\n      # running.\n      Process.exit(state.pid, :normal)\n    else\n      Beef.Users.set_online(state.user_id)\n    end\n\n    Process.monitor(pid)\n    {:reply, :ok, %{state | pid: pid}}\n  end\n\n  @all [{{:_, :\"$1\", :_}, [], [:\"$1\"]}]\n  def force_reconnects(rabbit_id) do\n    Onion.UserSessionRegistry\n    |> Registry.select(@all)\n    |> Enum.each(&reconnect(&1, rabbit_id))\n  end\n\n  def reconnect(user_pid, rabbit_id), do: GenServer.cast(user_pid, {:reconnect, rabbit_id})\n\n  defp reconnect_impl(voice_server_id, state) do\n    if state.pid || state.current_room_id do\n      case Onion.RoomSession.get(state.current_room_id, :voice_server_id) do\n        ^voice_server_id ->\n          room = Rooms.get_room_by_id(state.current_room_id)\n          Kousa.Room.join_vc_room(state.user_id, room)\n\n        _ ->\n          :ignore\n      end\n    end\n\n    {:noreply, state}\n  end\n\n  ##############################################################################\n  ## MESSAGING API.\n  ## TODO: change the first one to a call\n\n  defp handle_disconnect(pid, state = %{pid: pid}) do\n    Beef.Users.set_offline(state.user_id)\n\n    if state.current_room_id do\n      Kousa.Room.leave_room(state.user_id, state.current_room_id)\n    end\n\n    {:stop, :normal, state}\n  end\n\n  defp handle_disconnect(_, state), do: {:noreply, state}\n\n  #############################################################################\n  ## ROUTER\n\n  def handle_cast({:set, key, value}, state), do: set_impl(key, value, state)\n\n  def handle_cast({:send_ws, platform, msg}, state),\n    do: send_ws_impl(platform, msg, state)\n\n  def handle_cast({:reconnect, voice_server_id}, state),\n    do: reconnect_impl(voice_server_id, state)\n\n  def handle_cast({:set_mute, value}, state), do: set_mute_impl(value, state)\n  def handle_cast({:set_deafen, value}, state), do: set_deafen_impl(value, state)\n  def handle_cast({:new_tokens, tokens}, state), do: new_tokens_impl(tokens, state)\n  def handle_cast({:set_state, info}, state), do: set_state_impl(info, state)\n\n  def handle_call(:get_info_for_msg, reply, state), do: get_info_for_msg_impl(reply, state)\n  def handle_call({:get, key}, reply, state), do: get_impl(key, reply, state)\n  def handle_call({:set_active_ws, pid}, reply, state), do: set_active_ws(pid, reply, state)\n\n  def handle_info({:DOWN, _ref, :process, pid, _reason}, state), do: handle_disconnect(pid, state)\nend\n"
  },
  {
    "path": "kousa/lib/onion/voice_online_rabbit.ex",
    "content": "# used to alert the elixir server when the voice server starts up for the first time\ndefmodule Onion.VoiceOnlineRabbit do\n  use GenServer, restart: :temporary\n  use AMQP\n\n  defmodule State do\n    @type t :: %{\n            id: String.t(),\n            chan: map()\n          }\n\n    defstruct id: \"\", chan: nil\n  end\n\n  def start_supervised(voice_id) do\n    DynamicSupervisor.start_child(\n      Onion.VoiceOnlineRabbitDynamicSupervisor,\n      {__MODULE__, voice_id}\n    )\n  end\n\n  def start_link(voice_id) do\n    GenServer.start_link(\n      __MODULE__,\n      voice_id,\n      name: via(voice_id)\n    )\n  end\n\n  defp via(voice_id), do: {:via, Registry, {Onion.VoiceOnlineRabbitRegistry, voice_id}}\n\n  # @send_exchange \"shawarma_exchange\"\n  @online_exchange \"kousa_online_exchange\"\n  @online_receive_queue \"kousa_online_queue\"\n\n  def init(voice_id) do\n    {:ok, conn} =\n      Connection.open(Application.get_env(:kousa, :rabbit_url, \"amqp://guest:guest@localhost\"))\n\n    {:ok, chan} = Channel.open(conn)\n    setup_queue(voice_id, chan)\n\n    :ok = Basic.qos(chan, prefetch_count: 1)\n    queue_to_consume = @online_receive_queue <> voice_id\n    IO.puts(\"queue_to_consume_online: \" <> queue_to_consume)\n    # Register the GenServer process as a consumer\n    {:ok, _consumer_tag} = Basic.consume(chan, queue_to_consume, nil, no_ack: true)\n\n    {:ok, %State{chan: chan, id: voice_id}}\n  end\n\n  def handle_info({:basic_consume_ok, %{consumer_tag: _consumer_tag}}, state) do\n    {:noreply, state}\n  end\n\n  # Sent by the broker when the consumer is unexpectedly cancelled (such as after a queue deletion)\n  def handle_info({:basic_cancel, %{consumer_tag: _consumer_tag}}, state) do\n    {:stop, :normal, state}\n  end\n\n  # Confirmation sent by the broker to the consumer process after a Basic.cancel\n  def handle_info({:basic_cancel_ok, %{consumer_tag: _consumer_tag}}, state) do\n    {:noreply, state}\n  end\n\n  def handle_info(\n        {:basic_deliver, payload, %{delivery_tag: _tag, redelivered: _redelivered}},\n        %State{} = state\n      ) do\n    case Jason.decode!(payload) do\n      %{\"op\" => \"online\"} ->\n        Onion.UserSession.force_reconnects(state.id)\n\n      _ ->\n        :ok\n    end\n\n    # You might want to run payload consumption in separate Tasks in production\n    # consume(chan, tag, redelivered, payload)\n    {:noreply, state}\n  end\n\n  defp setup_queue(id, chan) do\n    {:ok, _} = Queue.declare(chan, @online_receive_queue <> id, durable: true)\n\n    :ok = Exchange.fanout(chan, @online_exchange <> id, durable: true)\n    :ok = Queue.bind(chan, @online_receive_queue <> id, @online_exchange <> id)\n  end\nend\n"
  },
  {
    "path": "kousa/lib/onion/voice_rabbit.ex",
    "content": "defmodule Onion.VoiceRabbit do\n  use GenServer\n  use AMQP\n\n  defmodule State do\n    @type t :: %{\n            id: String.t(),\n            chan: map()\n          }\n\n    defstruct id: \"\", chan: nil\n  end\n\n  def start_supervised(voice_id) do\n    DynamicSupervisor.start_child(\n      Onion.VoiceRabbitDynamicSupervisor,\n      {__MODULE__, voice_id}\n    )\n  end\n\n  def start_link(voice_id) do\n    GenServer.start_link(\n      __MODULE__,\n      voice_id,\n      name: via(voice_id)\n    )\n  end\n\n  defp via(voice_id), do: {:via, Registry, {Onion.VoiceRabbitRegistry, voice_id}}\n\n  # @send_exchange \"shawarma_exchange\"\n  @send_queue \"shawarma_queue\"\n  @receive_exchange \"kousa_exchange\"\n  @receive_queue \"kousa_queue\"\n\n  def init(voice_id) do\n    {:ok, conn} =\n      Connection.open(Application.get_env(:kousa, :rabbit_url, \"amqp://guest:guest@localhost\"))\n\n    {:ok, chan} = Channel.open(conn)\n    setup_queue(voice_id, chan)\n\n    queue_to_consume = @receive_queue <> voice_id\n    IO.puts(\"queue_to_consume: \" <> queue_to_consume)\n    # Register the GenServer process as a consumer\n    {:ok, _consumer_tag} = Basic.consume(chan, queue_to_consume, nil, no_ack: true)\n    {:ok, %State{chan: chan, id: voice_id}}\n  end\n\n  def send(id, msg) do\n    GenServer.cast(via(id), {:send, msg})\n  end\n\n  def handle_cast({:send, msg}, %State{chan: chan, id: id} = state) do\n    AMQP.Basic.publish(chan, \"\", @send_queue <> id, Jason.encode!(msg))\n    {:noreply, state}\n  end\n\n  def handle_info({:basic_consume_ok, %{consumer_tag: _consumer_tag}}, state) do\n    {:noreply, state}\n  end\n\n  # Sent by the broker when the consumer is unexpectedly cancelled (such as after a queue deletion)\n  def handle_info({:basic_cancel, %{consumer_tag: _consumer_tag}}, state) do\n    {:stop, :normal, state}\n  end\n\n  # Confirmation sent by the broker to the consumer process after a Basic.cancel\n  def handle_info({:basic_cancel_ok, %{consumer_tag: _consumer_tag}}, state) do\n    {:noreply, state}\n  end\n\n  def handle_info(\n        {:basic_deliver, payload, %{delivery_tag: _tag, redelivered: _redelivered}},\n        %State{} = state\n      ) do\n    data = Jason.decode!(payload)\n\n    case data do\n      %{\"uid\" => user_id} ->\n        Onion.UserSession.send_ws(user_id, nil, Map.delete(data, \"uid\"))\n\n      %{\"rid\" => room_id} ->\n        Onion.RoomSession.broadcast_ws(\n          room_id,\n          Map.delete(data, \"rid\")\n        )\n    end\n\n    # You might want to run payload consumption in separate Tasks in production\n    # consume(chan, tag, redelivered, payload)\n    {:noreply, state}\n  end\n\n  defp setup_queue(id, chan) do\n    {:ok, _} = Queue.declare(chan, @send_queue <> id, durable: true)\n    {:ok, _} = Queue.declare(chan, @receive_queue <> id, durable: true)\n\n    :ok = Exchange.fanout(chan, @receive_exchange <> id, durable: true)\n    :ok = Queue.bind(chan, @receive_queue <> id, @receive_exchange <> id)\n  end\nend\n"
  },
  {
    "path": "kousa/mix.exs",
    "content": "defmodule Kousa.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :kousa,\n      version: \"0.2.1\",\n      elixir: \"~> 1.11\",\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls],\n      preferred_cli_env: [\n        coveralls: :test,\n        \"coveralls.html\": :test\n      ],\n      elixirc_paths: elixirc_paths(Mix.env()),\n      aliases: aliases()\n    ]\n  end\n\n  def application do\n    dev_only_apps = List.wrap(if Mix.env() == :dev, do: :remix)\n    test_only_apps = List.wrap(if Mix.env() == :test, do: :websockex)\n\n    [\n      mod: {Kousa, []},\n      # moved logger to 2nd position to kill this error\n      # calling logger:remove_handler(default) failed: :error {:badmatch, {:error, {:not_found, :default}}}\n      extra_applications:\n        [:amqp, :logger, :ueberauth_github, :ueberauth_google, :prometheus_ex] ++\n          dev_only_apps ++ test_only_apps\n    ]\n  end\n\n  defp deps do\n    [\n      {:amqp, \"~> 2.1\"},\n      {:plug_cowboy, \"~> 2.5\"},\n      {:phoenix_pubsub, \"~> 2.0.0\"},\n      {:ecto_sql, \"~> 3.0\"},\n      {:ecto_enum, \"~> 1.4\"},\n      {:jason, \"~> 1.2\"},\n      {:joken, \"~> 2.0\"},\n      {:elixir_uuid, \"~> 1.2\"},\n      {:net_address, \"~> 0.3\"},\n      # TODO: switch off of httpoison to, e.g. Mojito or Finch\n      {:httpoison, \"~> 1.8\"},\n      {:finch, \"~> 0.6\"},\n      {:sentry, \"~> 8.0\"},\n      {:postgrex, \">= 0.0.0\"},\n      {:remix, \"~> 0.0.1\", only: :dev},\n      {:ueberauth, \"~> 0.6\"},\n      {:ueberauth_github, \"~> 0.7\"},\n      {:oauther, \"~> 1.1\"},\n      {:extwitter, \"~> 0.12\"},\n      {:ueberauth_twitter, \"~> 0.3\"},\n      {:ueberauth_google, \"~> 0.10\"},\n      {:prometheus_ex, \"~> 3.0\"},\n      {:prometheus_plugs, \"~> 1.1.1\"},\n      {:timex, \"~> 3.6\"},\n      # style ENFORCEMENT\n      {:credo, \"~> 1.5.5\"},\n      # test helpers\n      {:faker, \"~> 0.16.0\", only: :test},\n      {:excoveralls, \"~> 0.10\", only: :test},\n      {:ueberauth_discord, \"~> 0.5.2\"},\n      {:websockex, \"~> 0.4.3\", only: :test}\n    ]\n  end\n\n  defp elixirc_paths(:test), do: [\"lib\", \"test/_support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  defp aliases do\n    [\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n      test: [\"ecto.create --quiet\", \"ecto.migrate\", \"test\"]\n    ]\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210124203315_most_tables.exs",
    "content": "defmodule Beef.Repo.Migrations.MostTables do\n  use Ecto.Migration\n\n  def change do\n    execute(\"CREATE EXTENSION IF NOT EXISTS \\\"uuid-ossp\\\";\", \"\")\n\n    create table(:users, primary_key: false) do\n      add :id, :uuid, primary_key: true, default: fragment(\"uuid_generate_v4()\")\n      add :githubId, :text, null: false\n      add :username, :text, null: false\n      add :bio, :text, default: \"\"\n      add :avatarUrl, :text, null: false\n      add :tokenVersion, :integer, default: 1\n      add :numFollowing, :integer, default: 0\n      add :numFollowers, :integer, default: 0\n      add :online, :boolean, default: false\n      add :lastOnline, :naive_datetime\n\n      timestamps()\n    end\n\n    create unique_index(:users, [:githubId])\n    create unique_index(:users, [:username])\n\n    create table(:rooms, primary_key: false) do\n      add :id, :uuid, primary_key: true, default: fragment(\"uuid_generate_v4()\")\n      add :name, :string, null: false\n      add :numPeopleInside, :integer, default: 0\n\n      add :creatorId, references(:users, on_delete: :delete_all, type: :uuid), null: false\n\n      timestamps()\n    end\n\n    create unique_index(:rooms, [:creatorId])\n\n    alter table(:users) do\n      add :currentRoomId, references(:rooms, type: :uuid, on_delete: :nilify_all)\n      add :modForRoomId, references(:rooms, type: :uuid, on_delete: :nilify_all)\n      add :canSpeakForRoomId, references(:rooms, type: :uuid, on_delete: :nilify_all)\n    end\n\n    create table(:followers, primary_key: false) do\n      add :userId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :followerId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n\n      timestamps()\n    end\n\n    create table(:room_blocks, primary_key: false) do\n      add :userId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :roomId, references(:rooms, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :modId, references(:users, on_delete: :delete_all, type: :uuid), null: false\n\n      timestamps()\n    end\n\n    create table(:user_blocks, primary_key: false) do\n      add :userId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :userIdBlocked, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210125155346_more_fields.exs",
    "content": "defmodule Beef.Repo.Migrations.MoreFields do\n  use Ecto.Migration\n\n  def change do\n    alter table(:rooms) do\n      add :isPrivate, :boolean, default: true\n      add(:peoplePreviewList, {:array, :map}, default: [])\n    end\n\n    alter table(:users) do\n      add :displayName, :string, default: \"\"\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210202143344_has_logged_in.exs",
    "content": "defmodule Beef.Repo.Migrations.HasLoggedIn do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :hasLoggedIn, :boolean, default: false\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210202162325_default_timestamps.exs",
    "content": "defmodule Beef.Repo.Migrations.DefaultTimestamps do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      modify :inserted_at, :naive_datetime, null: false, default: fragment(\"now()\")\n      modify :updated_at, :naive_datetime, null: false, default: fragment(\"now()\")\n    end\n    alter table(:followers) do\n      modify :inserted_at, :naive_datetime, null: false, default: fragment(\"now()\")\n      modify :updated_at, :naive_datetime, null: false, default: fragment(\"now()\")\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210210005241_email.exs",
    "content": "defmodule Beef.Repo.Migrations.Email do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :email, :text, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210210012609_access_token.exs",
    "content": "defmodule Beef.Repo.Migrations.AccessToken do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      modify :email, :text, null: true\n      add :githubAccessToken, :text\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210212141759_bans.exs",
    "content": "defmodule Beef.Repo.Migrations.Bans do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :reasonForBan, :text\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210214140430_twitter_id.exs",
    "content": "defmodule Beef.Repo.Migrations.TwitterId do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :twitterId, :text\n    end\n\n    create unique_index(:users, [:email])\n    create unique_index(:users, [:twitterId])\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210214172930_github_id_nullable.exs",
    "content": "defmodule Beef.Repo.Migrations.GithubIdNullable do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      modify :githubId, :text, null: true\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210219173153_case_insensitive_unique_username_index.exs",
    "content": "defmodule Beef.Repo.Migrations.CaseInsensitiveUniqueUsernameIndex do\n  use Ecto.Migration\n\n  def change do\n    execute(\"drop index users_username_index;\", \"\")\n    execute(\"create unique index users_username_index on users(lower(username));\", \"\")\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210221041850_room_permissions.exs",
    "content": "defmodule Beef.Repo.Migrations.RoomPermissions do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      remove :modForRoomId\n      remove :canSpeakForRoomId\n    end\n\n    create table(:room_permissions, primary_key: false) do\n      add :userId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :roomId, references(:rooms, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :isSpeaker, :boolean, default: false\n      add :isMod, :boolean, default: false\n      add :askedToSpeak, :boolean, default: false\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210221233129_voice_server_id.exs",
    "content": "defmodule Beef.Repo.Migrations.VoiceServerId do\n  use Ecto.Migration\n\n  def change do\n    alter table(:rooms) do\n      add :voiceServerId, :string, default: \"\"\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210301151808_use_utc_datetime_usec.exs",
    "content": "defmodule Beef.Repo.Migrations.UseUtcDatetimeUsec do\n  use Ecto.Migration\n\n  def change do\n    # https://dba.stackexchange.com/questions/134385/convert-postgres-timestamp-to-timestamptz\n    Enum.each([\"followers\", \"room_blocks\", \"room_permissions\", \"rooms\", \"user_blocks\", \"users\"], fn table_name ->\n      execute(\"\"\"\n              ALTER TABLE #{table_name}\n              ALTER inserted_at TYPE timestamptz USING inserted_at AT TIME ZONE 'UTC'\n              , ALTER inserted_at SET DEFAULT now();\n              \"\"\", \"\")\n      execute(\"\"\"\n              ALTER TABLE #{table_name}\n              ALTER updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC'\n              , ALTER updated_at SET DEFAULT now();\n              \"\"\", \"\")\n    end)\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210301162635_last_online_utc_datetime_usec.exs",
    "content": "defmodule Beef.Repo.Migrations.LastOnlineUtcDatetimeUsec do\n  use Ecto.Migration\n\n  def change do\n    execute(\n      \"\"\"\n      ALTER TABLE users\n      ALTER \"lastOnline\" TYPE timestamptz USING \"lastOnline\" AT TIME ZONE 'UTC'\n      , ALTER \"lastOnline\" SET DEFAULT now();\n      \"\"\",\n      \"\"\n    )\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210301200955_scheduled_room.exs",
    "content": "defmodule Beef.Repo.Migrations.ScheduledRoom do\n  use Ecto.Migration\n\n  def change do\n    create table(:scheduled_rooms, primary_key: false) do\n      add :id, :uuid, primary_key: true, default: fragment(\"uuid_generate_v4()\")\n      add :name, :text, null: false\n      add :numAttending, :integer, default: 0\n      add :scheduledFor, :utc_datetime_usec, null: false\n      add :description, :text, null: false, default: \"\"\n\n      add :creatorId, references(:users, on_delete: :delete_all, type: :uuid), null: false\n      add :roomId, references(:rooms, on_delete: :nilify_all, type: :uuid), null: true\n\n      add :inserted_at, :utc_datetime_usec, null: false, default: fragment(\"now()\")\n      add :updated_at, :utc_datetime_usec, null: false, default: fragment(\"now()\")\n    end\n\n    create table(:attending_scheduled_rooms, primary_key: false) do\n      add :userId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :scheduledRoomId, references(:scheduled_rooms, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n\n      add :inserted_at, :utc_datetime_usec, null: false, default: fragment(\"now()\")\n      add :updated_at, :utc_datetime_usec, null: false, default: fragment(\"now()\")\n    end\n\n    create table(:scheduled_room_cohosts, primary_key: false) do\n      add :userId, references(:users, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n      add :scheduledRoomId, references(:scheduled_rooms, on_delete: :delete_all, type: :uuid), null: false, primary_key: true\n\n      add :inserted_at, :utc_datetime_usec, null: false, default: fragment(\"now()\")\n      add :updated_at, :utc_datetime_usec, null: false, default: fragment(\"now()\")\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210303204515_room_description.exs",
    "content": "defmodule Beef.Repo.Migrations.RoomDescription do\n  use Ecto.Migration\n\n  def change do\n    alter table(:rooms) do\n      add :description, :text, default: \"\", null: false\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210304004945_set_timezone_for_now.exs",
    "content": "defmodule Beef.Repo.Migrations.SetTimezoneForNow do\n  use Ecto.Migration\n\n  def change do\n    {:ok, %{rows: [[ db_name ]]}} = Ecto.Adapters.SQL.query(Beef.Repo, \"select current_database()\")\n\n    execute(\"ALTER DATABASE \"<>db_name<>\" SET timezone TO 'UTC';\", \"\")\n    execute(\"SET timezone TO 'UTC';\", \"\")\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210305031538_scheduled_room_started.exs",
    "content": "defmodule Beef.Repo.Migrations.ScheduledRoomStarted do\n  use Ecto.Migration\n\n  def change do\n    alter table(:scheduled_rooms) do\n      add :started, :boolean, null: false, default: false\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210321141614_discord_login.exs",
    "content": "defmodule Beef.Repo.Migrations.DiscordLogin do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :discordId, :string, null: true, default: nil\n      add :discordAccessToken, :string, null: true, default: nil\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210424191849_banner_url.exs",
    "content": "defmodule Beef.Repo.Migrations.BannerUrl do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :bannerUrl, :text\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210425000426_user_bot_api_key.exs",
    "content": "defmodule Beef.Repo.Migrations.UserBotApiKey do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :botOwnerId, references(:users, on_delete: :delete_all, type: :uuid)\n      add :apiKey, :uuid\n    end\n\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210427180800_user_ip.exs",
    "content": "defmodule Beef.Repo.Migrations.UserIp do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :ip, :text\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210427192138_room_ban_ip.exs",
    "content": "defmodule Beef.Repo.Migrations.RoomBanIp do\n  use Ecto.Migration\n\n  def change do\n    alter table(:room_blocks) do\n      add :ip, :text\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210501201857_emails_are_no_longer_unique.exs",
    "content": "defmodule Beef.Repo.Migrations.EmailsAreNoLongerUnique do\n  use Ecto.Migration\n\n  def change do\n    drop index(:users, [:email])\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210504210047_whisper_privacy_setting.exs",
    "content": "defmodule Beef.Repo.Migrations.WhisperPrivacySetting do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :whisperPrivacySetting, :text, default: \"on\"\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210504225546_chat_mode.exs",
    "content": "defmodule Beef.Repo.Migrations.ChatMode do\n  use Ecto.Migration\n\n  def change do\n    alter table(:rooms) do\n      add :autoSpeaker, :boolean, default: false\n      add :chatMode, :string, default: \"default\"\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210506000509_chat_throttle.exs",
    "content": "defmodule Beef.Repo.Migrations.ChatThrottle do\n  use Ecto.Migration\n\n  def change do\n    alter table(:rooms) do\n      add :chatThrottle, :int, default: 1000\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/priv/repo/migrations/20210509055314_admin_update.exs",
    "content": "defmodule Beef.Repo.Migrations.AdminUpdate do\n  use Ecto.Migration\n\n  def change do\n    alter table(:users) do\n      add :staff, :boolean, default: false\n      add :contributions, :integer, default: 0\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/_support/deprecations.ex",
    "content": "defmodule KousaTest.Support.Deprecations do\n  import ExUnit.Assertions\n  import ExUnit.CaptureLog\n\n  # TODO: add versioning stuff in here\n  def capture_deprecation(fun) do\n    log = capture_log(fun)\n\n    assert log =~ \"error\"\n    assert log =~ \"is deprecated\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/_support/ecto_sandbox.ex",
    "content": "defmodule KousaTest.Support.EctoSandbox do\n  # see https://hexdocs.pm/ecto/testing-with-ecto.html\n\n  defmacro __using__(_) do\n    quote do\n      def checkout_ecto_sandbox(tags) do\n        :ok = Ecto.Adapters.SQL.Sandbox.checkout(Beef.Repo)\n\n        unless tags[:async] do\n          Ecto.Adapters.SQL.Sandbox.mode(Beef.Repo, {:shared, self()})\n        end\n\n        :ok\n      end\n\n      setup :checkout_ecto_sandbox\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/_support/factory.ex",
    "content": "defmodule KousaTest.Support.Factory do\n  @moduledoc \"\"\"\n  defines the `create/2` function.\n\n  Parameter 1:  The module for the schema representing the database table\n  You are trying to populate.\n\n  Parameter 2: any fields we would like to override.\n  \"\"\"\n\n  alias Beef.Repo\n  alias Beef.Schemas.User\n  alias Beef.Schemas.ScheduledRoom\n  alias Beef.Schemas.Room\n\n  def create(struct, data \\\\ [])\n\n  def create(User, data) do\n    merged_data =\n      Keyword.merge(\n        [\n          githubId: Faker.Internet.user_name(),\n          twitterId: Faker.Internet.user_name(),\n          displayName: Faker.Internet.user_name(),\n          username: String.slice(String.replace(Faker.Internet.user_name(), \".\", \"_\"), 0..14),\n          email: Faker.Internet.free_email(),\n          githubAccessToken: \"ntoaunthanuheoh\",\n          avatarUrl: \"https://example.com/abc.jpg\",\n          bannerUrl: \"https://example.com/abc.jpg\",\n          bio: \"a dogehouse user\",\n          tokenVersion: 1,\n          numFollowing: 0,\n          numFollowers: 0\n        ],\n        data\n      )\n\n    User\n    |> struct(merged_data)\n    |> Repo.insert!(returning: true)\n  end\n\n  def create(Room, data) do\n    # if we don't specify the creatorId, then pre-emptively\n    # create a new user to be the creator.\n    creator_id =\n      Keyword.get_lazy(data, :creatorId, fn ->\n        create(User).id\n      end)\n\n    merged_data =\n      Keyword.merge(\n        [\n          name: Faker.Company.buzzword(),\n          numPeopleInside: 1,\n          isPrivate: false,\n          voiceServerId: UUID.uuid4(),\n          creatorId: creator_id,\n          peoplePreviewList: []\n        ],\n        data\n      )\n\n    Room\n    |> struct(merged_data)\n    |> Repo.insert!(returning: true)\n  end\n\n  def create(ScheduledRoom, data) do\n    # build a userId by creating a user id, if it\n    # doesn't exist\n    creator_id =\n      Keyword.get_lazy(\n        data,\n        :creatorId,\n        fn -> create(User).id end\n      )\n\n    merged_data =\n      Keyword.merge(\n        [\n          name: Faker.Company.buzzword(),\n          description: \"\",\n          numAttendees: 0,\n          creatorId: creator_id,\n          scheduledFor: DateTime.utc_now() |> Timex.shift(days: 1)\n        ],\n        data\n      )\n\n    ScheduledRoom\n    |> struct(merged_data)\n    |> Repo.insert!(returning: true)\n  end\nend\n"
  },
  {
    "path": "kousa/test/_support/http_request.ex",
    "content": "defmodule BrothTest.HttpRequest do\n  def post(path, body, opts \\\\ []) do\n    callers =\n      self()\n      |> :erlang.term_to_binary()\n      |> Base.encode16()\n\n    case :post\n         |> Finch.build(\n           \"http://localhost:4001\" <> path,\n           [{\"content-type\", \"application/json\"}, {\"user-agent\", callers}] ++ opts,\n           Jason.encode!(body)\n         )\n         |> Finch.request(BrothHttpRequests) do\n      {:ok, %Finch.Response{body: body, status: 200}} ->\n        {:ok, Jason.decode!(body)}\n\n      x ->\n        x\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/_support/message.ex",
    "content": "defmodule BrothTest.Support.Message do\n  import Ecto.Changeset\n  alias Kousa.Utils.Errors\n\n  import Kousa.Utils.Version, only: [sigil_v: 2]\n\n  @init %{user: %{id: UUID.uuid4()}}\n\n  def validate(message, state \\\\ @init) do\n    message\n    # TODO: make this version match the mix project version.\n    |> Map.put(\"version\", ~v(0.2.0))\n    |> Broth.Message.changeset(state)\n    |> apply_action(:validate)\n    |> case do\n      {:ok, msg = %{errors: nil}} ->\n        case apply_action(msg.payload, :validate) do\n          {:ok, payload} ->\n            {:ok, %{msg | payload: payload}}\n\n          {:error, changeset} ->\n            {:error, %{msg | payload: %{}, errors: Errors.changeset_errors(changeset)}}\n        end\n\n      # this one has errors\n      {:ok, msg} ->\n        {:error, msg}\n\n      error ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/_support/ws_client.ex",
    "content": "defmodule BrothTest.WsClient do\n  use WebSockex\n\n  @api_url Application.compile_env!(:kousa, :api_url)\n\n  def child_spec(info) do\n    # just make the id be a random uuid.\n    info\n    |> super\n    |> Map.put(:id, UUID.uuid4())\n  end\n\n  def start_link(_opts) do\n    ancestors =\n      :\"$ancestors\"\n      |> Process.get()\n      |> :erlang.term_to_binary()\n      |> Base.encode16()\n\n    @api_url\n    |> Path.join(\"socket\")\n    |> WebSockex.start_link(__MODULE__, nil,\n      extra_headers: [{\"user-agent\", ancestors}, {\"x-forwarded-for\", \"127.0.0.1\"}]\n    )\n  end\n\n  ###########################################################################\n  # API\n\n  # an elaboration on the send_msg that represents the equivalent of\n  # \"fetching\" / \"calling\" operations from the user.\n  def send_call(client_ws, op, payload) do\n    call_ref = UUID.uuid4()\n\n    WebSockex.cast(\n      client_ws,\n      {:send, %{\"op\" => op, \"p\" => payload, \"ref\" => call_ref, \"v\" => \"0.2.0\"}}\n    )\n\n    call_ref\n  end\n\n  def send_call_legacy(client_ws, op, payload) do\n    call_ref = UUID.uuid4()\n\n    WebSockex.cast(\n      client_ws,\n      {:send, %{\"op\" => op, \"d\" => payload, \"fetchId\" => call_ref}}\n    )\n\n    call_ref\n  end\n\n  @doc \"\"\"\n  performs the call AND traps its reply.  Should not be used for anything which is\n  the primary call under test; only to be used for supporting calls necessary as a\n  part of the setup.\n  \"\"\"\n  def do_call(ws, op, payload) do\n    ref = send_call(ws, op, payload)\n    reply_op = op <> \":reply\"\n\n    receive do\n      {:text, %{\"op\" => ^reply_op, \"ref\" => ^ref, \"p\" => payload}, ^ws} ->\n        payload\n    after\n      100 ->\n        raise \"reply to `#{op}` not received\"\n    end\n  end\n\n  def do_call_legacy(ws, op, payload) do\n    ref = send_call_legacy(ws, op, payload)\n\n    receive do\n      {:text, %{\"op\" => _, \"fetchId\" => ^ref, \"d\" => payload}, ^ws} ->\n        payload\n    after\n      100 ->\n        raise \"reply to `#{op}` not received\"\n    end\n  end\n\n  def send_msg(client_ws, op, payload),\n    do: WebSockex.cast(client_ws, {:send, %{\"op\" => op, \"p\" => payload, \"v\" => \"0.2.0\"}})\n\n  def send_msg_legacy(client_ws, op, payload),\n    do: WebSockex.cast(client_ws, {:send, %{\"op\" => op, \"d\" => payload}})\n\n  defp send_msg_impl(map, test_pid) do\n    {:reply, {:text, Jason.encode!(map)}, test_pid}\n  end\n\n  def forward_frames(client_ws), do: WebSockex.cast(client_ws, {:forward_frames, self()})\n  defp forward_frames_impl(test_pid, _state), do: {:ok, test_pid}\n\n  defmacro assert_frame(op, payload, from \\\\ nil) do\n    if from do\n      quote do\n        from = unquote(from)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => unquote(op), \"p\" => unquote(payload)}, ^from}\n        )\n      end\n    else\n      quote do\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => unquote(op), \"p\" => unquote(payload)}, _}\n        )\n      end\n    end\n  end\n\n  defmacro assert_frame_legacy(op, payload, from \\\\ nil) do\n    if from do\n      quote do\n        from = unquote(from)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => unquote(op), \"d\" => unquote(payload)}, ^from}\n        )\n      end\n    else\n      quote do\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => unquote(op), \"d\" => unquote(payload)}, _}\n        )\n      end\n    end\n  end\n\n  @doc \"\"\"\n  asserts that a reply from a previously issued call operation has been\n  receieved, as identified by its reference uuid (`ref`).\n\n  Note that the third parameter is matchable, so you can use `_`, use\n  it to assign a to a variable, or, do partial matches on maps.\n  \"\"\"\n  defmacro assert_reply(op, ref, payload, from \\\\ nil) do\n    if from do\n      quote do\n        op = unquote(op)\n        from = unquote(from)\n        ref = unquote(ref)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => ^op, \"p\" => unquote(payload), \"ref\" => ^ref}, ^from}\n        )\n      end\n    else\n      quote do\n        op = unquote(op)\n        ref = unquote(ref)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => ^op, \"p\" => unquote(payload), \"ref\" => ^ref}, _}\n        )\n      end\n    end\n  end\n\n  @doc \"\"\"\n  asserts that an error has been returned from a previously issued call or\n  cast operation has been received, as identified by its reference uuid (`ref`).\n\n  Note that the third parameter is matchable, so you can use `_`, use\n  it to assign a to a variable, or, do partial matches on the error.\n  \"\"\"\n  defmacro assert_error(op, ref, error, from \\\\ nil) do\n    if from do\n      quote do\n        op = unquote(op)\n        from = unquote(from)\n        ref = unquote(ref)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => ^op, \"e\" => unquote(error), \"ref\" => ^ref}, ^from}\n        )\n      end\n    else\n      quote do\n        op = unquote(op)\n        ref = unquote(ref)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => ^op, \"e\" => unquote(error), \"ref\" => ^ref}, _}\n        )\n      end\n    end\n  end\n\n  defmacro assert_reply_legacy(ref, payload, from \\\\ nil) do\n    if from do\n      quote do\n        from = unquote(from)\n        ref = unquote(ref)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => \"fetch_done\", \"d\" => unquote(payload), \"fetchId\" => ^ref}, ^from}\n        )\n      end\n    else\n      quote do\n        ref = unquote(ref)\n\n        ExUnit.Assertions.assert_receive(\n          {:text, %{\"op\" => \"fetch_done\", \"d\" => unquote(payload), \"fetchId\" => ^ref}, _}\n        )\n      end\n    end\n  end\n\n  # TODO: change off of Process.link and switch to Proce\n  defmacro assert_dies(client_ws, fun, reason, timeout \\\\ 100) do\n    quote bind_quoted: [client_ws: client_ws, fun: fun, reason: reason, timeout: timeout] do\n      Process.flag(:trap_exit, true)\n      Process.link(client_ws)\n      fun.()\n      ExUnit.Assertions.assert_receive({:EXIT, ^client_ws, ^reason}, timeout)\n    end\n  end\n\n  defmacro refute_frame(op, from) do\n    quote do\n      from = unquote(from)\n      ExUnit.Assertions.refute_receive({:text, %{\"op\" => unquote(op)}, ^from})\n    end\n  end\n\n  ###########################################################################\n  # ROUTER\n\n  @impl true\n  def handle_frame({type, data}, test_pid) do\n    send(test_pid, {type, Jason.decode!(data), self()})\n    {:ok, test_pid}\n  end\n\n  @impl true\n  def handle_cast({:send, map}, test_pid), do: send_msg_impl(map, test_pid)\n  def handle_cast({:forward_frames, test_pid}, state), do: forward_frames_impl(test_pid, state)\nend\n\ndefmodule BrothTest.WsClientFactory do\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  require WsClient\n\n  import ExUnit.Assertions\n\n  # note that this function ALSO causes the calling process to be subscribed\n  # to forwarded messages from the websocket client.\n  def create_client_for(user = %User{}, opts \\\\ []) do\n    tokens = Kousa.Utils.TokenUtils.create_tokens(user)\n\n    # start and link the websocket client\n    client_ws = ExUnit.Callbacks.start_supervised!(WsClient)\n    WsClient.forward_frames(client_ws)\n\n    if opts[:legacy] do\n      WsClient.send_msg(client_ws, \"auth\", %{\n        \"accessToken\" => tokens.accessToken,\n        \"refreshToken\" => tokens.refreshToken,\n        \"platform\" => \"foo\",\n        \"reconnectToVoice\" => false,\n        \"muted\" => false,\n        \"deafened\" => false\n      })\n\n      WsClient.assert_frame_legacy(\"auth-good\", _)\n    else\n      WsClient.do_call(client_ws, \"auth:request\", %{\n        \"accessToken\" => tokens.accessToken,\n        \"refreshToken\" => tokens.refreshToken,\n        \"platform\" => \"foo\",\n        \"reconnectToVoice\" => false,\n        \"muted\" => false,\n        \"deafened\" => false\n      })\n    end\n\n    # link the UserProcess to prevent dangling DB sandbox lookups\n    [{usersession_pid, _}] = Registry.lookup(Onion.UserSessionRegistry, user.id)\n    # associate the user session with the database.\n    Process.link(usersession_pid)\n\n    client_ws\n  end\nend\n"
  },
  {
    "path": "kousa/test/ad_hoc_user_test.exs",
    "content": "defmodule KousaTest.AdHocUserTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  @moduledoc \"\"\"\n  ad-hoc test set to give coverage for all modules\n  that have 'alias Beef.Schemas.User', prior to refactoring.\n  \"\"\"\n\n  # TODO: recategorize into appropriate test cases over\n  # time.\n\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n\n  alias Beef.Repo\n  alias KousaTest.Support.Factory\n\n  describe \"Beef.Schemas.RoomBlock\" do\n    alias Beef.Schemas.RoomBlock\n\n    test \"you can add a room blocker into the roomblock table\" do\n      %{id: uid} = Factory.create(User)\n      %{id: rid} = Factory.create(Room)\n      %{id: mid} = Factory.create(User)\n\n      assert {:ok, %RoomBlock{userId: ^uid, roomId: ^rid, modId: ^mid}} =\n               %RoomBlock{}\n               |> RoomBlock.insert_changeset(%{userId: uid, roomId: rid, modId: mid})\n               |> Repo.insert()\n\n      assert [roomblock] = Repo.all(RoomBlock)\n\n      assert %RoomBlock{\n               userId: ^uid,\n               user: %User{id: ^uid},\n               roomId: ^rid,\n               # TODO: insert room assoc here.\n               modId: ^mid,\n               mod: %User{id: ^mid}\n             } = Repo.preload(roomblock, [:user, :mod])\n    end\n  end\n\n  describe \"Kousa.Utils.TokenUtils\" do\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/follow_test.exs",
    "content": "defmodule Kousa.Beef.FollowTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  @moduledoc \"\"\"\n  ad-hoc test set to give coverage for all modules\n  that have 'alias Beef.Schemas.User', prior to refactoring.\n  \"\"\"\n\n  # TODO: recategorize into appropriate test cases over\n  # time.\n\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Follow\n  alias Beef.Follows\n  alias Beef.Users\n\n  alias Beef.Repo\n  alias KousaTest.Support.Factory\n\n  describe \"for Beef.Follow\" do\n    test \"you can safely insert a beef users into follows table\" do\n      %{id: id1} = Factory.create(User)\n      %{id: id2} = Factory.create(User)\n\n      assert {:ok, %Follow{userId: ^id1, followerId: ^id2}} =\n               %Follow{}\n               |> Follow.insert_changeset(%{userId: id1, followerId: id2})\n               |> Repo.insert()\n\n      assert [follow] = Repo.all(Follow)\n\n      assert %Follow{\n               userId: ^id1,\n               user: %User{id: ^id1},\n               followerId: ^id2,\n               follower: %User{id: ^id2}\n             } = Repo.preload(follow, [:user, :follower])\n    end\n\n    test \"search_username orders by numFollowers\" do\n      Factory.create(User, [{:username, \"user1\"}, {:numFollowers, 3}])\n      Factory.create(User, [{:username, \"user2\"}, {:numFollowers, 2}])\n\n      assert [%{username: \"user1\"}, %{username: \"user2\"}] = Users.search_username(\"user\")\n\n      # creates user with most followers\n      Factory.create(User, [{:username, \"user3\"}, {:numFollowers, 4}])\n\n      assert [%{username: \"user3\"}, %{username: \"user1\"}, %{username: \"user2\"}] =\n               Users.search_username(\"user\")\n    end\n  end\n\n  describe \"Follows\" do\n    test \"get_followers_online_and_not_in_a_room/1\" do\n      user = Factory.create(User)\n      follower = Factory.create(User)\n\n      # no followers\n      assert [] = Follows.get_followers_online_and_not_in_a_room(user.id)\n\n      # make id1 a follower of id2\n      %Follow{}\n      |> Follow.insert_changeset(%{\n        userId: user.id,\n        followerId: follower.id\n      })\n      |> Repo.insert()\n\n      # still no followers\n      assert [] = Follows.get_followers_online_and_not_in_a_room(user.id)\n\n      # make user online\n      Users.set_online(follower.id)\n\n      uid = user.id\n      fid = follower.id\n\n      assert [follower] = Follows.get_followers_online_and_not_in_a_room(user.id)\n\n      assert %Follow{\n               userId: ^uid,\n               user: %User{id: ^uid},\n               followerId: ^fid,\n               follower: %User{id: ^fid}\n             } = Repo.preload(follower, [:user, :follower])\n    end\n\n    test \"bulk_insert/1\" do\n      uid = Factory.create(User).id\n      fid1 = Factory.create(User).id\n      fid2 = Factory.create(User).id\n\n      assert {2, _} =\n               Follows.bulk_insert([\n                 %{userId: uid, followerId: fid1},\n                 %{userId: uid, followerId: fid2}\n               ])\n\n      assert [\n               %Follow{\n                 user: %User{id: ^uid},\n                 follower: %User{id: ^fid1}\n               },\n               %Follow{\n                 user: %User{id: ^uid},\n                 follower: %User{id: ^fid2}\n               }\n             ] =\n               Follow\n               |> Repo.all()\n               |> Repo.preload([:user, :follower])\n    end\n\n    test \"following_me?\" do\n      uid = Factory.create(User).id\n      fid = Factory.create(User).id\n\n      refute Follows.following_me?(uid, fid)\n\n      Follows.insert(%{userId: uid, followerId: fid})\n\n      assert Follows.following_me?(uid, fid)\n    end\n\n    test \"fetch_invite_list/2\" do\n      uid = Factory.create(User).id\n      fid1 = Factory.create(User).id\n      fid2 = Factory.create(User).id\n\n      Follows.bulk_insert([\n        %{userId: uid, followerId: fid1},\n        %{userId: uid, followerId: fid2}\n      ])\n\n      assert {[], _} = Follows.fetch_invite_list(uid)\n\n      # but only make follower1 online\n      Users.set_online(fid1)\n\n      assert {[%User{id: ^fid1}], _} = Follows.fetch_invite_list(uid)\n    end\n\n    @tag :skip\n    test \"get followers/2\" do\n      uid = Factory.create(User).id\n      fid1 = Factory.create(User).id\n      fid2 = Factory.create(User).id\n\n      Follows.bulk_insert([\n        %{userId: uid, followerId: fid1},\n        %{userId: uid, followerId: fid2}\n      ])\n\n      # not really sure how the call signature here works.\n      Follows.get_followers(uid, uid)\n    end\n\n    @tag :skip\n    test \"get_following/2\" do\n      # same issue as before.\n    end\n\n    test \"delete/2\" do\n      uid = Factory.create(User).id\n      fid1 = Factory.create(User).id\n      fid2 = Factory.create(User).id\n\n      Follows.bulk_insert([\n        %{userId: uid, followerId: fid1},\n        %{userId: uid, followerId: fid2}\n      ])\n\n      # not really sure how the call signature here works.\n      assert [_, _] = Repo.all(Follow)\n\n      Follows.delete(uid, fid2)\n\n      assert [\n               %{\n                 userId: ^uid,\n                 followerId: ^fid1\n               }\n             ] = Repo.all(Follow)\n    end\n\n    test \"insert/1\" do\n      uid = Factory.create(User).id\n      fid = Factory.create(User).id\n\n      Follows.insert(%{userId: uid, followerId: fid})\n\n      assert [\n               %Follow{\n                 userId: ^uid,\n                 followerId: ^fid\n               }\n             ] = Repo.all(Follow)\n    end\n\n    test \"get_info\" do\n      uid = Factory.create(User).id\n      fid = Factory.create(User).id\n\n      assert %{followsYou: false, youAreFollowing: false} = Follows.get_info(uid, fid)\n\n      Follows.insert(%{userId: uid, followerId: fid})\n\n      assert %{followsYou: true, youAreFollowing: false} = Follows.get_info(uid, fid)\n\n      Follows.insert(%{userId: fid, followerId: uid})\n\n      assert %{followsYou: true, youAreFollowing: true} = Follows.get_info(uid, fid)\n\n      Follows.delete(uid, fid)\n\n      assert %{followsYou: false, youAreFollowing: true} = Follows.get_info(uid, fid)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/room_test.exs",
    "content": "defmodule Kousa.Beef.RoomTest do\n  # allow tests to run in parallel\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias KousaTest.Support.Factory\n  alias Beef.Schemas.Room\n  alias Beef.Schemas.User\n  alias Beef.Repo\n\n  describe \"Beef.Room\" do\n    test \"you can add a room into the room table\" do\n      %{id: cid} = Factory.create(User)\n      vid = UUID.uuid4()\n\n      assert {:ok,\n              %Room{\n                creatorId: ^cid,\n                name: \"my room\",\n                isPrivate: false,\n                voiceServerId: ^vid\n              }} =\n               %Room{}\n               |> Room.insert_changeset(%{\n                 name: \"my room\",\n                 numPeopleInside: 0,\n                 isPrivate: false,\n                 creatorId: cid,\n                 voiceServerId: vid\n               })\n               |> Repo.insert()\n\n      assert [room] = Repo.all(Room)\n\n      assert %Room{\n               ####################################\n               # NOTE these two don't match up.\n               creatorId: ^cid,\n               user: %User{id: ^cid},\n               ####################################\n               name: \"my room\",\n               isPrivate: false,\n               voiceServerId: ^vid\n             } = Repo.preload(room, [:user])\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/rooms_test.exs",
    "content": "defmodule Kousa.Beef.RoomsTest do\n  # allow tests to run in parallel\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias KousaTest.Support.Factory\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n  alias Beef.Rooms\n  alias Beef.Repo\n  alias Beef.RoomBlocks\n\n  describe \"Rooms\" do\n    # need to mock user sessions\n    # not working atm\n    @tag :skip\n    test \"get_room_status\" do\n      user = Factory.create(User)\n      # if not in room\n      assert Rooms.get_room_status(user.id) == {nil, nil}\n\n      # if user is creator of room\n      # room = Factory.create(Room, %{creatorId: user.id})\n      # Rooms.join_room(room, user.id)\n      # assert Rooms.get_room_status(user.id) == {:creator, room}\n\n      # if user it not creator\n      # listener\n      # room = Factory.create(Room)\n      # Beef.Rooms.join_room(room, user.id)\n      # assert Rooms.get_room_status(user.id) == {:listener, room}\n\n      # # mod\n      # Beef.Data.RoomPermission.insert(%{ userId: user.id, roomId: room.id, isMod: true })\n      # assert Rooms.get_room_status(user.id) == {:mod, room}\n\n      # # speaker\n      # Beef.Data.RoomPermission.upsert(%{ userId: user.id, roomId: room.id }, %{ isMod: false, isSpeaker: true })\n      # assert Rooms.get_room_status(user.id) == {:speaker, room}\n\n      # # askedToSpeak\n      # Beef.Data.RoomPermission.upsert(%{ userId: user.id, roomId: room.id }, %{ isMod: false, isSpeaker: false, askedToSpeak: true })\n      # assert Rooms.get_room_status(user.id) == {:askedToSpeak, room}\n    end\n\n    test \"search_name\" do\n      room = Factory.create(Room)\n      id = room.id\n      assert [%{id: ^id}] = Rooms.search_name(room.name)\n      assert [%{id: ^id}] = Rooms.search_name(String.slice(room.name, 0..2))\n      assert [] = Rooms.search_name(\"akljdsjoqwdijo12\")\n      room2 = Factory.create(Room, name: \"qiwodqwjdioqwdjiqwo\", isPrivate: true)\n      assert [] = Rooms.search_name(room2.name)\n    end\n\n    test \"search_name orders by desc numPeopleInside\" do\n      %Room{\n        id: id\n      } = Factory.create(Room, [{:name, \"room1\"}])\n\n      Beef.Rooms.increment_room_people_count(id)\n      assert %Room{numPeopleInside: 2} = Repo.get!(Room, id)\n\n      %Room{\n        id: id\n      } = Factory.create(Room, [{:name, \"room2\"}])\n\n      # make sure room2 has 1 person inside\n      assert %Room{numPeopleInside: 1} = Repo.get!(Room, id)\n\n      # check if room1 shows up first as it has 2 ppl inside\n      assert [%{name: \"room1\"}, %{name: \"room2\"}] = Rooms.search_name(\"room\")\n\n      Beef.Rooms.increment_room_people_count(id)\n      Beef.Rooms.increment_room_people_count(id)\n\n      # make sure room2 has 3 people inside\n      assert %Room{numPeopleInside: 3} = Repo.get!(Room, id)\n\n      # check if room2 shows up first as it has 3 ppl inside\n      assert [%{name: \"room2\"}, %{name: \"room1\"}] = Rooms.search_name(\"room\")\n    end\n\n    test \"can_join_room\" do\n      u = Factory.create(User)\n\n      # non existent room\n      assert Beef.Rooms.can_join_room(\"f6834652-b876-4641-9639-c5d04d87dc96\", u.id) ==\n               {:error, \"room doesn't exist anymore\"}\n\n      max_room_size = Application.fetch_env!(:kousa, :max_room_size)\n\n      room = Factory.create(Room, [{:numPeopleInside, max_room_size}])\n      assert Beef.Rooms.can_join_room(room.id, u.id) == {:error, \"room is full\"}\n\n      room2 = Factory.create(Room)\n      RoomBlocks.insert(%{userId: u.id, roomId: room2.id, modId: u.id})\n      assert Beef.Rooms.can_join_room(room2.id, u.id) == {:error, \"you are blocked from the room\"}\n\n      creator = Factory.create(User)\n      room3 = Factory.create(Room, [{:creatorId, creator.id}])\n      Beef.UserBlocks.insert(%{userId: creator.id, userIdBlocked: u.id})\n\n      assert Beef.Rooms.can_join_room(room3.id, u.id) ==\n               {:error, \"the creator of the room blocked you\"}\n\n      room4 = Factory.create(Room)\n      assert Beef.Rooms.can_join_room(room4.id, u.id) == {:ok, room4}\n    end\n\n    # still need to get to\n    @tag :skip\n    test \"get_top_public_rooms\" do\n    end\n\n    test \"get_room_by_id\" do\n      room = Factory.create(Room)\n      room2 = Factory.create(Room)\n      assert Beef.Rooms.get_room_by_id(room.id) == room\n      assert Beef.Rooms.get_room_by_id(room2.id) == room2\n    end\n\n    # still need to get to\n    @tag :skip\n    test \"get_next_creator_for_room\" do\n    end\n\n    test \"get_a_user_for_room\" do\n      room = Factory.create(Room)\n      userForRoom = Factory.create(User, [{:currentRoomId, room.id}])\n      _notUserForRoom = Factory.create(User)\n\n      assert Beef.Rooms.get_a_user_for_room(room.id) == userForRoom\n    end\n\n    test \"get_room_by_creator_id\" do\n      u = Factory.create(User)\n      createdByU = Factory.create(Room, [{:creatorId, u.id}])\n      notCreatedByU = Factory.create(Room)\n\n      assert Beef.Rooms.get_room_by_creator_id(u.id) == createdByU\n      refute Beef.Rooms.get_room_by_creator_id(u.id) == notCreatedByU\n    end\n\n    test \"owner?\" do\n      u = Factory.create(User)\n      r = Factory.create(Room)\n\n      assert !Beef.Rooms.owner?(r.id, u.id)\n\n      r2 = Factory.create(Room, [{:creatorId, u.id}])\n      assert Beef.Rooms.owner?(r2.id, u.id)\n    end\n\n    test \"all_rooms\" do\n      Factory.create(Room)\n      Factory.create(Room)\n      Factory.create(Room)\n\n      assert [%Room{}, %Room{}, %Room{}] = Beef.Rooms.all_rooms()\n    end\n\n    # MUTATION tests\n    test \"set_room_privacy_by_creator_id\" do\n      %User{id: id} = Factory.create(User)\n      r = Factory.create(Room, [{:isPrivate, false}, {:name, \"dogeroom\"}, {:creatorId, id}])\n      assert !r.isPrivate and r.name == \"dogeroom\" and r.creatorId == id\n\n      Beef.Rooms.set_room_privacy_by_creator_id(id, true, \"newdogeroom\")\n\n      assert %{\n               isPrivate: true,\n               creatorId: ^id,\n               name: \"newdogeroom\"\n             } = Repo.get!(Room, r.id)\n    end\n\n    @tag :skip\n    test \"join_room\" do\n    end\n\n    test \"increment_room_people_count/1\" do\n      %Room{\n        id: id,\n        numPeopleInside: numPeopleInside\n      } = Factory.create(Room)\n\n      assert numPeopleInside == 1\n\n      Beef.Rooms.increment_room_people_count(id)\n      assert %Room{numPeopleInside: 2} = Repo.get!(Room, id)\n    end\n\n    @tag :skip\n    test \"increment_room_people_count/2\" do\n    end\n\n    test \"delete_room_by_id\" do\n      %Room{id: id} = Factory.create(Room)\n      assert %Room{} = Repo.get!(Room, id)\n\n      Beef.Rooms.delete_room_by_id(id)\n      assert [] = Beef.Rooms.all_rooms()\n    end\n\n    @tag :skip\n    test \"decrement_room_people_count\" do\n    end\n\n    @tag :skip\n    # this isn't working atm, there is a problem with setting new_people_list?\n    test \"set_room_owner_and_dec\" do\n      # u = Factory.create(User)\n      # %{\n      #   id: new_owner_id,\n      #   displayName: new_display_name,\n      #   numFollowers: new_num_followers\n      #   } = Factory.create(User)\n      # r = Factory.create(Room, [\n      #   { :numPeopleInside, 3 },\n      #   { :creatorId, u.id }\n      #   ])\n\n      # assert r.creatorId == u.id\n\n      # Beef.Rooms.set_room_owner_and_dec(\n      #   r.id,\n      #   new_owner_id,\n      #   [%{\n      #     id: new_owner_id,\n      #     displayName: new_display_name,\n      #     numFollowers: new_num_followers\n      # }])\n\n      # assert %{\n      #   creatorId: ^new_owner_id,\n      #   numPeopleInside: 2\n      # } = Repo.get!(Room, r.id)\n    end\n\n    @tag :skip\n    test \"leave_room\" do\n    end\n\n    test \"raw_insert\" do\n      creator = Factory.create(User)\n\n      Beef.Rooms.raw_insert(\n        %{\n          name: \"cool room\",\n          creatorId: creator.id\n        },\n        [\n          %{\n            id: creator.id,\n            displayName: creator.displayName,\n            numFollowers: creator.numFollowers,\n            avatarUrl: creator.avatarUrl\n          }\n        ]\n      )\n\n      creator2 = Factory.create(User)\n\n      Beef.Rooms.raw_insert(\n        %{\n          name: \"another cool room\",\n          creatorId: creator2.id\n        },\n        [\n          %{\n            id: creator2.id,\n            displayName: creator2.displayName,\n            numFollowers: creator2.numFollowers,\n            avatarUrl: creator2.avatarUrl\n          }\n        ]\n      )\n\n      assert [%Room{}, %Room{}] = Repo.all(Room)\n    end\n\n    test \"update_name\" do\n      creator = Factory.create(User)\n      r = Factory.create(Room, [{:creatorId, creator.id}])\n\n      refute r.name == \"new cool name\"\n      Beef.Rooms.update_name(creator.id, \"new cool name\")\n\n      assert %Room{\n               name: \"new cool name\"\n             } = Repo.get!(Room, r.id)\n    end\n\n    test \"create\" do\n      %User{\n        avatarUrl: avatarUrl,\n        displayName: displayName,\n        numFollowers: numFollowers,\n        id: id\n      } = Factory.create(User)\n\n      {:ok, r} =\n        Beef.Rooms.create(%{\n          creatorId: id,\n          name: \"dogeruum\",\n          description: \"a place to doge\",\n          isPrivate: false\n        })\n\n      assert %Room{\n               peoplePreviewList: [\n                 %User.Preview{\n                   avatarUrl: ^avatarUrl,\n                   displayName: ^displayName,\n                   numFollowers: ^numFollowers,\n                   id: ^id\n                 }\n               ]\n             } = Repo.get!(Room, r.id)\n    end\n\n    test \"edit\" do\n      r = Factory.create(Room)\n      refute r.name == \"updated name\"\n\n      {:ok, %Room{id: id}} =\n        Beef.Rooms.edit(r.id, %{\n          name: \"updated name\",\n          isPrivate: true,\n          description: \"updated description\"\n        })\n\n      assert %{\n               id: ^id,\n               name: \"updated name\",\n               isPrivate: true,\n               description: \"updated description\"\n             } = Beef.Rooms.get_room_by_id(id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/scheduled_room_test.exs",
    "content": "defmodule Kousa.Beef.ScheduledRoomsTest do\n  # allow tests to run in parallel\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias KousaTest.Support.Factory\n  alias Beef.Repo\n  alias Beef.Schemas.User\n  alias Beef.Schemas.ScheduledRoom\n  alias Beef.ScheduledRooms\n\n  defp create_user(_) do\n    {:ok, user: Factory.create(User)}\n  end\n\n  describe \"you can create a scheduled room\" do\n    setup :create_user\n\n    @scheduled_room_input %{\n      name: \"test scheduled room\",\n      description: \"\",\n      numAttendees: 0,\n      scheduledFor:\n        DateTime.utc_now()\n        |> Timex.shift(days: 1)\n        |> Timex.format!(\"{ISO:Extended:Z}\")\n    }\n\n    test \"with ISO date\", %{user: user} do\n      {:ok, sr} = ScheduledRooms.insert(Map.put(@scheduled_room_input, :creatorId, user.id))\n\n      assert [^sr] = Repo.all(ScheduledRoom)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/user_block_test.exs",
    "content": "defmodule Kousa.Beef.UserBlockTest do\n  # allow tests to run in parallel\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias KousaTest.Support.Factory\n  alias Beef.Schemas.User\n  alias Beef.Repo\n\n  describe \"Beef.Schemas.UserBlock\" do\n    alias Beef.Schemas.UserBlock\n\n    test \"you can add a user block\" do\n      %{id: uid} = Factory.create(User)\n      %{id: bid} = Factory.create(User)\n\n      assert {:ok,\n              %UserBlock{\n                userId: ^uid,\n                userIdBlocked: ^bid\n              }} =\n               %UserBlock{}\n               |> UserBlock.insert_changeset(%{\n                 userId: uid,\n                 userIdBlocked: bid\n               })\n               |> Repo.insert()\n\n      assert [user_block] = Repo.all(UserBlock)\n\n      assert %UserBlock{\n               userId: ^uid,\n               user: %User{id: ^uid},\n               ####################################\n               # NOTE these two don't match up.\n               userIdBlocked: ^bid,\n               blockedUser: %User{id: ^bid}\n               ####################################\n             } = Repo.preload(user_block, [:user, :blockedUser])\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/user_blocks_test.exs",
    "content": "defmodule Kousa.Beef.UserBlocksTest do\n  # allow tests to run in parallel\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias KousaTest.Support.Factory\n  alias Beef.UserBlocks\n  alias Beef.Schemas.UserBlock\n  # alias\n\n  alias Beef.Schemas.User\n  # alias Beef.Repo\n\n  describe \"UserBlocks\" do\n    test \"insert\" do\n      %{id: uid} = Factory.create(User)\n      %{id: bid} = Factory.create(User)\n\n      assert {:ok, ub = %UserBlock{}} = UserBlocks.insert(%{userId: uid, userIdBlocked: bid})\n      assert ub.userId == uid\n      assert ub.userIdBlocked == bid\n    end\n\n    test \"blocked?\" do\n      %{id: uid} = Factory.create(User)\n      %{id: bid} = Factory.create(User)\n\n      {:ok, %UserBlock{}} = UserBlocks.insert(%{userId: uid, userIdBlocked: bid})\n\n      assert UserBlocks.blocked?(uid, bid)\n      assert !UserBlocks.blocked?(bid, uid)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/user_test.exs",
    "content": "defmodule Kousa.Beef.UserTest do\n  # allow tests to run in parallel\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias KousaTest.Support.Factory\n  alias Beef.Follows\n  alias Beef.Schemas.Room\n  alias Beef.Schemas.User\n  alias Beef.UserBlocks\n  alias Beef.Users\n  alias Beef.Repo\n\n  describe \"you can create a user\" do\n    @gh_input %{\n      \"id\" => 12345,\n      \"avatar_url\" => \"https://foo.bar/baz.jpg\",\n      \"banner_url\" => \"https://foo.bar/baz.jpg\",\n      \"name\" => \"tester\",\n      \"bio\" => \"test\",\n      \"github_access_token\" => \"askldjlqwjldq\"\n    }\n\n    test \"with github\" do\n      {:create, user} = Users.github_find_or_create(@gh_input, \"foo-access-token\")\n\n      [\n        ^user\n      ] = Repo.all(User)\n    end\n  end\n\n  defp create_user(_) do\n    {:ok, user: Factory.create(User)}\n  end\n\n  describe \"when you query a user\" do\n    setup :create_user\n\n    # @todo figure out how to get ecto to do this:\n    # for now I hacked it by returning: true on insert\n    # NB: this fails because the databases are currently not configured to\n    # autogenerate UUIDs.\n    test \"by user_id\", %{user: user = %{id: id}} do\n      assert [^id] = Users.find_by_github_ids([user.githubId])\n    end\n\n    test \"by searching username\", %{user: %{id: id, username: username}} do\n      assert [%{id: ^id}] = Users.search_username(\"@\" <> username)\n      assert [%{id: ^id}] = Users.search_username(username)\n      assert [%{id: ^id}] = Users.search_username(String.slice(username, 0..2))\n      assert [] = Users.search_username(\"akljdsjoqwdijo12\")\n    end\n  end\n\n  describe \"when you edit a user\" do\n    setup :create_user\n\n    test \"it forbids a too short username\", %{user: %{id: id}} do\n      assert {:error, _} =\n               Users.edit_profile(\n                 id,\n                 %{username: \"tim\", displayName: \"tim\", bio: \"\"}\n               )\n    end\n\n    test \"with avatar_url that is from twitter\", %{user: %{id: id}} do\n      assert {:ok, user} =\n               Users.edit_profile(id, %{\n                 username: \"timmy\",\n                 displayName: \"tim\",\n                 bio: \"\",\n                 avatarUrl:\n                   \"https://pbs.twimg.com/profile_images/1214953675724079106/6Y3XokVC_200x200.jpg\"\n               })\n    end\n\n    test \"with avatar_url that is from github\", %{user: %{id: id}} do\n      assert {:ok, user} =\n               Users.edit_profile(id, %{\n                 username: \"timmy\",\n                 displayName: \"tim\",\n                 bio: \"\",\n                 avatarUrl: \"https://avatars.githubusercontent.com/u/35400192?v=4\"\n               })\n    end\n\n    test \"with avatar_url that is from discord\", %{user: %{id: id}} do\n      assert {:ok, user} =\n               Users.edit_profile(id, %{\n                 username: \"timmy\",\n                 displayName: \"tim\",\n                 bio: \"\",\n                 avatarUrl:\n                   \"https://cdn.discordapp.com/avatars/473965680857972757/6b3e4b9be1fd453230172ca6509f0b46.webp\"\n               })\n    end\n\n    test \"with avatar_url that is not from twitter/github/discord\", %{user: %{id: id}} do\n      assert {:error, _} =\n               Users.edit_profile(id, %{\n                 username: \"timmy\",\n                 displayName: \"tim\",\n                 bio: \"\",\n                 avatarUrl:\n                   \"https://bit.ly/3dzG9DB#https://avatars.githubusercontent.com/u/44095206?v=4\"\n               })\n    end\n\n    test \"with empty bio\", %{user: %{id: id}} do\n      assert {:ok, user} =\n               Users.edit_profile(id, %{\n                 username: \"timmy\",\n                 displayName: \"tim\",\n                 bio: \"\",\n                 avatarUrl:\n                   \"https://pbs.twimg.com/profile_images/1152793238761345024/VRBvxeCM_400x400.jpg\"\n               })\n\n      assert \"\" = user.bio\n    end\n  end\n\n  describe \"to mutate a user\" do\n    setup :create_user\n\n    # see issue, re: test above.\n    test \"you can use set_online/1 and set_offline/1\", %{user: user = %{username: _username}} do\n      [id] = Users.find_by_github_ids([user.githubId])\n\n      Users.set_online(id)\n\n      assert %{online: true} = Users.get_by_id(id)\n\n      Users.set_offline(id)\n\n      assert %{online: false} = Users.get_by_id(id)\n    end\n  end\n\n  describe \"Users.delete/1\" do\n    setup :create_user\n\n    test \"deletes a user\", %{user: user} do\n      Users.delete(user.id)\n      assert is_nil(Users.get_by_id(user.id))\n    end\n\n    test \"cascades correctly\", %{user: user1} do\n      user2 = Factory.create(User)\n\n      Follows.insert(%{userId: user1.id, followerId: user2.id})\n      Factory.create(Room, creatorId: user1.id)\n      room = Factory.create(Room, creatorId: user2.id)\n      Beef.RoomBlocks.insert(%{roomId: room.id, userId: user1.id, modId: user2.id})\n      UserBlocks.insert(%{userIdBlocked: user1.id, userId: user2.id})\n      Beef.RoomPermissions.ask_to_speak(user1.id, room.id)\n      assert {:ok, _} = Users.delete(user1.id)\n\n      # probably needs some more tests here.\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/beef/users_test.exs",
    "content": "defmodule Kousa.Beef.UsersTest do\n  # what a terrible module name!\n  # TODO: organize this into the correct context.\n\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.Room\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias KousaTest.Support.Factory\n  alias Beef.Repo\n\n  describe \"Users\" do\n    setup do\n      {:ok, user: Factory.create(User)}\n    end\n\n    test \"edit_profile\", %{user: user} do\n      # TODO: probably you want this to take a user\n      # struct as the first parameter.\n      refute user.bio == \"updated bio\"\n\n      {:ok, %User{id: id}} =\n        Users.edit_profile(user.id, %{\n          bio: \"updated bio\",\n          username: \"dave\",\n          displayName: \"bar\",\n          avatarUrl: \"https://www.pbs.twimg.com/profile_images/ghi.jpg\",\n          bannerUrl:\n            \"https://pbs.twimg.com/profile_banners/840626569743912960/1601562221/1500x500\"\n        })\n\n      assert %{\n               id: ^id,\n               bio: \"updated bio\",\n               username: \"dave\",\n               displayName: \"bar\",\n               avatarUrl: \"https://www.pbs.twimg.com/profile_images/ghi.jpg\",\n               bannerUrl:\n                 \"https://pbs.twimg.com/profile_banners/840626569743912960/1601562221/1500x500\"\n             } = Repo.get!(User, user.id)\n    end\n\n    test \"search\", %{user: user} do\n      # TODO: make offset default to zero\n      assert {[%User{}], _} = Users.search(user.username, 0)\n\n      assert {[%User{}], _} = Users.search(user.displayName, 0)\n\n      assert {[], _} = Users.search(\"foobarbaz\", 0)\n\n      # TODO: more tests on stuff like how search\n      # interacts with rooms.  This needs to be specced\n      # out by Ben.\n    end\n\n    test \"bulk_insert\" do\n      Users.bulk_insert([\n        %{\n          bio: \"lorem ipsum\",\n          username: \"david\",\n          displayName: \"d0\",\n          avatarUrl: \"https://foo.bar/d0\",\n          bannerUrl: \"https://foo.bar/d0\"\n        },\n        %{\n          bio: \"dolor sunt\",\n          username: \"karen\",\n          displayName: \"d1\",\n          avatarUrl: \"https://foo.bar/d1\",\n          bannerUrl: \"https://foo.bar/d1\"\n        }\n      ])\n\n      assert [%User{}, %User{}, %User{}] = Repo.all(User)\n    end\n\n    test \"find_by_github_ids\", %{user: user} do\n      Users.bulk_insert([\n        %{\n          bio: \"lorem ipsum\",\n          username: \"david\",\n          displayName: \"d0\",\n          avatarUrl: \"https://foo.bar/d0\",\n          bannerUrl: \"https://foo.bar/d0\",\n          githubId: \"abcdef\"\n        },\n        %{\n          bio: \"dolor sunt\",\n          username: \"karen\",\n          displayName: \"d1\",\n          avatarUrl: \"https://foo.bar/d1\",\n          bannerUrl: \"https://foo.bar/d1\",\n          githubId: \"ghijkl\"\n        }\n      ])\n\n      # note that there is one entry in there already.\n      assert [_, _, _] = Repo.all(User)\n\n      assert [_, _] = Users.find_by_github_ids([\"abcdef\", \"ghijkl\"])\n      assert [user.id] == Users.find_by_github_ids([user.githubId])\n    end\n\n    test \"inc_num_following/2\", %{user: user} do\n      assert %{numFollowing: 0} = Repo.get(User, user.id)\n\n      Users.inc_num_following(user.id, 2)\n\n      assert %{numFollowing: 2} = Repo.get(User, user.id)\n    end\n\n    # LOLZ JK this won't work until we have mocked room pools.\n    @tag :skip\n    test \"get_users_in_current_room\", %{user: user} do\n      # build a room\n      %{id: rid} = Factory.create(Room, creatorId: user.id)\n\n      assert {nil, []} = Users.get_users_in_current_room(user.id)\n\n      # attach the user to the room.\n      Users.set_current_room(user.id, rid)\n\n      Repo.all(User)\n\n      Users.get_users_in_current_room(user.id)\n    end\n\n    test \"get_by_id\", %{user: %{id: id}} do\n      assert %User{id: ^id} = Users.get_by_id(id)\n    end\n\n    test \"get_by_username\", %{user: user} do\n      assert user.id == Users.get_by_username(user.username).id\n    end\n\n    test \"set_reason_for_ban\", %{user: user} do\n      Users.set_reason_for_ban(user.id, \"bad human\")\n\n      assert %User{reasonForBan: \"bad human\"} = Repo.get(User, user.id)\n    end\n\n    test \"get_by_id_with_current_room\", %{user: user} do\n      assert %User{currentRoom: nil} = Users.get_by_id_with_current_room(user.id)\n\n      # build a room\n      %{id: rid} = Factory.create(Room, creatorId: user.id)\n\n      # attach the user to the room.\n      Users.set_current_room(user.id, rid)\n\n      assert %User{currentRoom: %Room{id: ^rid}} = Users.get_by_id_with_current_room(user.id)\n    end\n\n    test \"set_online\", %{user: user} do\n      Users.set_online(user.id)\n\n      assert %User{online: true} = Repo.get(User, user.id)\n    end\n\n    test \"set_user_left_current_room\", %{user: user} do\n      # build a room\n      %{id: rid} = Factory.create(Room, creatorId: user.id)\n\n      # attach the user to the room.\n      Users.set_current_room(user.id, rid)\n\n      assert %User{currentRoomId: ^rid} = Repo.get(User, user.id)\n\n      Users.set_user_left_current_room(user.id)\n\n      assert %User{currentRoomId: nil} = Repo.get(User, user.id)\n    end\n\n    test \"set offline\", %{user: user} do\n      test_time = DateTime.utc_now()\n\n      Users.set_online(user.id)\n\n      assert %User{\n               online: true,\n               lastOnline: creation_time\n             } = Repo.get(User, user.id)\n\n      # NOTE: old_time was set at account creation.\n      assert DateTime.compare(test_time, creation_time) == :gt\n\n      Users.set_offline(user.id)\n\n      assert %User{online: false, lastOnline: last_online_time} = Repo.get(User, user.id)\n\n      # is this really what we want?\n      assert creation_time == last_online_time\n    end\n\n    test \"get_current_room_id returns nil\", %{user: %{id: id}} do\n      refute Users.get_current_room_id(id)\n    end\n\n    @tag :skip\n    # doesn't work on account of no mocked room process pool\n    test \"get_current_room\"\n\n    @tag :skip\n    # see above.\n    test \"set_current_room\"\n\n    @tag :skip\n    test \"twitter_find_or_create\"\n\n    @tag :skip\n    test \"github_find_or_create\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/_THIS DIRECTORY TO BE DEPRECATED",
    "content": ""
  },
  {
    "path": "kousa/test/broth/_calls/create_room_from_scheduled_room_test.exs",
    "content": "defmodule BrothTest.CreateRoomFromScheduledRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket create_room_from_scheduled_room operation\" do\n    @tag :skip\n    test \"converts a scheduled room to a real room\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/create_room_test.exs",
    "content": "defmodule BrothTest.CreateRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket create_room operation\" do\n    test \"creates a new room\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"create_room\",\n          %{\n            \"name\" => \"foo room\",\n            \"description\" => \"baz quux\",\n            \"privacy\" => \"private\"\n          }\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"room\" => %{\n            \"creatorId\" => ^user_id,\n            \"description\" => \"baz quux\",\n            \"id\" => room_id,\n            \"name\" => \"foo room\",\n            \"isPrivate\" => true\n          }\n        }\n      )\n\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/delete_scheduled_room_test.exs",
    "content": "defmodule BrothTest.DeleteScheduledRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket delete_scheduled_room operation\" do\n    test \"deletes a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      {:ok, %{id: room_id}} =\n        Kousa.ScheduledRoom.schedule(user_id, %{\n          \"name\" => \"foo room\",\n          \"scheduledFor\" => time\n        })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"delete_scheduled_room\",\n          %{\"id\" => room_id}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{}\n      )\n\n      refute Beef.ScheduledRooms.get_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/edit_profile_test.exs",
    "content": "defmodule BrothTest.EditProfileTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket edit_profile operation\" do\n    test \"a username can be changed\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"edit_profile\",\n          %{\n            \"data\" => %{\n              \"username\" => \"new_username\"\n            }\n          }\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"username\" => \"new_username\"}\n      )\n\n      assert Users.get_by_id(user_id).username == \"new_username\"\n    end\n\n    test \"a username can't be changed to an existing username\", t do\n      user_id = t.user.id\n\n      user2 = Factory.create(User)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"edit_profile\",\n          %{\n            \"data\" => %{\n              \"username\" => user2.username\n            }\n          }\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"isUsernameTaken\" => true}\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/edit_room_test.exs",
    "content": "defmodule BrothTest.EditRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket edit_room operation\" do\n    test \"changes the name of the room you're in\", t do\n      user_id = t.user.id\n      # first, create a room owned by the primary user.\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"edit_room\",\n          %{\n            \"name\" => \"bar room\",\n            \"description\" => \"baz quux\",\n            \"privacy\" => \"private\",\n            \"chatThrottle\" => 1\n          }\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        true,\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"new_room_details\",\n        %{\n          \"description\" => \"baz quux\",\n          \"isPrivate\" => true,\n          \"chatThrottle\" => 1,\n          \"name\" => \"bar room\",\n          \"roomId\" => ^room_id\n        }\n      )\n\n      # TODO: make sure that privacy is actually set\n      assert %{\n               isPrivate: true,\n               description: \"baz quux\",\n               chatThrottle: 1,\n               name: \"bar room\"\n             } = Beef.Rooms.get_room_by_id(room_id)\n    end\n\n    @tag :skip\n    test \"when you're not in a room\", t do\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"edit_room\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        _,\n        t.client_ws\n      )\n    end\n\n    @tag :skip\n    test \"errors when you aren't the creator of the room\"\n\n    @tag :skip\n    test \"when the room doesn't exist\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/edit_scheduled_room_test.exs",
    "content": "defmodule BrothTest.EditScheduledRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket edit_scheduled_room operation\" do\n    test \"edits a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      {:ok, %{id: room_id}} =\n        Kousa.ScheduledRoom.schedule(user_id, %{\n          \"name\" => \"foo room\",\n          \"scheduledFor\" => time\n        })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"edit_scheduled_room\",\n          %{\"id\" => room_id, \"data\" => %{\"name\" => \"bar room\"}}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"name\" => \"bar room\"}\n      )\n\n      assert %{name: \"bar room\"} = Beef.ScheduledRooms.get_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/fetch_follow_list_test.exs",
    "content": "defmodule BrothTest.FetchFollowListTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket fetch_follow_list operation\" do\n    @tag :skip\n    test \"works\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/follow_info_test.exs",
    "content": "defmodule BrothTest.FollowInfoTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket follow_info operation\" do\n    test \"retrieves following info\", t do\n      user_id = t.user.id\n\n      followed = %{id: followed_id} = Factory.create(User)\n      followed_ws = WsClientFactory.create_client_for(followed)\n\n      Kousa.Follow.follow(t.user.id, followed_id, true)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"follow_info\",\n          %{\"userId\" => followed_id}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"followsYou\" => false,\n          \"youAreFollowing\" => true\n        },\n        t.client_ws\n      )\n\n      ref =\n        WsClient.send_call_legacy(\n          followed_ws,\n          \"follow_info\",\n          %{\"userId\" => user_id}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"followsYou\" => true,\n          \"youAreFollowing\" => false\n        },\n        followed_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/get_blocked_from_room_users_test.exs",
    "content": "defmodule BrothTest.GetBlockedFromRoomUsersTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket get_blocked_from_room_users operation\" do\n    test \"returns one banned user if you are in the room\", t do\n      user_id = t.user.id\n      # first, create a room owned by the primary user.\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      # make user to ban and put them in the room\n      user_to_ban = Factory.create(User)\n      user_to_ban_ws = WsClientFactory.create_client_for(user_to_ban)\n      WsClient.do_call(user_to_ban_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_to_ban.id)\n      Kousa.Room.block_from_room(user_id, user_to_ban.id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_blocked_from_room_users\",\n          %{}\n        )\n\n      banned_user_id = user_to_ban.id\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"users\" => [%{\"id\" => ^banned_user_id}]\n        },\n        t.client_ws\n      )\n    end\n\n    test \"returns what if you're not in a room\", t do\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_blocked_from_room_users\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"users\" => []\n        },\n        t.client_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/get_my_following_test.exs",
    "content": "defmodule BrothTest.GetMyFollowingTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket get_my_following operation\" do\n    test \"returns an empty list if you aren't following anyone\", t do\n      ref = WsClient.send_call_legacy(t.client_ws, \"get_my_following\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply_legacy(ref, %{\"users\" => []})\n    end\n\n    test \"returns that person if you are following someone\", t do\n      %{id: followed_id} = Factory.create(User)\n      Kousa.Follow.follow(t.user.id, followed_id, true)\n\n      ref = WsClient.send_call_legacy(t.client_ws, \"get_my_following\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply_legacy(ref, %{\n        \"users\" => [\n          %{\n            \"id\" => ^followed_id\n          }\n        ]\n      })\n    end\n\n    @tag :skip\n    test \"test proper pagination of get_my_following\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/get_my_scheduled_rooms_about_to_start_test.exs",
    "content": "defmodule BrothTest.GetMyScheduledRoomsAboutToStartTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket get_my_scheduled_rooms_about_to_start operation\" do\n    test \"returns no scheduled rooms\", t do\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_my_scheduled_rooms_about_to_start\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => []\n        }\n      )\n    end\n\n    test \"returns a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      Kousa.ScheduledRoom.schedule(user_id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_my_scheduled_rooms_about_to_start\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => [\n            %{\n              \"creator\" => %{\"id\" => ^user_id},\n              \"name\" => \"foo room\",\n              \"scheduledFor\" => the_future\n            }\n          ]\n        }\n      )\n\n      assert DateTime.to_iso8601(time) == the_future\n    end\n\n    test \"does not return someone else's scheduled room\", t do\n      other_user = Factory.create(User)\n\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n\n      Kousa.ScheduledRoom.schedule(other_user.id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_my_scheduled_rooms_about_to_start\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => []\n        }\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/get_scheduled_rooms_test.exs",
    "content": "defmodule BrothTest.GetScheduledRoomsTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket get_scheduled_rooms operation\" do\n    test \"returns no scheduled rooms\", t do\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_scheduled_rooms\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => []\n        }\n      )\n    end\n\n    test \"returns a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      Kousa.ScheduledRoom.schedule(user_id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_scheduled_rooms\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => [\n            %{\n              \"creator\" => %{\"id\" => ^user_id},\n              \"name\" => \"foo room\",\n              \"scheduledFor\" => the_future\n            }\n          ]\n        }\n      )\n\n      assert DateTime.to_iso8601(time) == the_future\n    end\n\n    test \"does return someone else's scheduled room\", t do\n      other_user = Factory.create(User)\n\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n\n      {:ok, %{id: room_id}} =\n        Kousa.ScheduledRoom.schedule(other_user.id, %{\n          \"name\" => \"foo room\",\n          \"scheduledFor\" => time\n        })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_scheduled_rooms\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => [%{\"id\" => ^room_id}]\n        }\n      )\n    end\n\n    test \"does not return someone else's scheduled room if getOnlyMyScheduledRooms is set\", t do\n      other_user = Factory.create(User)\n\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n\n      Kousa.ScheduledRoom.schedule(other_user.id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_scheduled_rooms\",\n          %{\"getOnlyMyScheduledRooms\" => true}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"scheduledRooms\" => []\n        }\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/get_top_public_rooms_test.exs",
    "content": "defmodule BrothTest.GetTopPublicRoomsTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket get_top_public_rooms operation\" do\n    test \"returns one public room if it's the only one\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_top_public_rooms\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"rooms\" => [%{\"id\" => ^room_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"doesn't return a room if it's private\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_top_public_rooms\",\n          %{}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"rooms\" => []},\n        t.client_ws\n      )\n    end\n\n    @tag :skip\n    test \"when there's more than one room\"\n\n    @tag :skip\n    test \"cursors also work\"\n\n    @tag :skip\n    test \"there is a maximum limit to the cursor\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/get_user_profile_test.exs",
    "content": "defmodule BrothTest.GetUserProfileTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket get_user_profile operation\" do\n    test \"can get your own user info\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"get_user_profile\",\n          %{\"userId\" => t.user.id}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"id\" => ^user_id\n        }\n      )\n    end\n\n    @tag :skip\n    test \"you can't stalk someone who has blocked you\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/join_room_and_get_info_test.exs",
    "content": "defmodule BrothTest.JoinRoomAndGetInfoTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket join_room_and_get_info operation\" do\n    test \"can be used to join a room\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      # create a user that is logged in.\n      joiner = %{id: joiner_id} = Factory.create(User)\n      joiner_ws = WsClientFactory.create_client_for(joiner)\n\n      ref =\n        WsClient.send_call_legacy(\n          joiner_ws,\n          \"join_room_and_get_info\",\n          %{\"roomId\" => room_id}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\n          \"roomId\" => ^room_id,\n          \"room\" => %{\"id\" => ^room_id},\n          \"users\" => users = [_, _]\n        },\n        joiner_ws\n      )\n\n      assert Enum.any?(users, &match?(%{\"id\" => ^user_id}, &1))\n      assert Enum.any?(users, &match?(%{\"id\" => ^joiner_id}, &1))\n\n      WsClient.assert_frame_legacy(\n        \"new_user_join_room\",\n        %{\"roomId\" => ^room_id, \"user\" => %{\"id\" => ^joiner_id}},\n        t.client_ws\n      )\n\n      # TODO: do a test to check to make sure the muted state is correct\n    end\n\n    @tag :skip\n    test \"does what if you're already in the room\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/schedule_room_test.exs",
    "content": "defmodule BrothTest.ScheduleRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket schedule_room operation\" do\n    test \"creates a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"schedule_room\",\n          %{\"name\" => \"foo room\", \"scheduledFor\" => DateTime.to_iso8601(time)}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"scheduledRoom\" => %{\"id\" => room_id, \"name\" => \"foo room\"}}\n      )\n\n      assert %{name: \"foo room\"} = Beef.ScheduledRooms.get_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/search_test.exs",
    "content": "defmodule BrothTest.SearchTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket misc:search operation\" do\n    test \"returns public room if query matches\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"search\",\n          %{query: \"foo\"}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"items\" => [%{\"id\" => ^room_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"returns public room if query matches (mixed search)\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"search\",\n          %{query: \"foo\"}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"rooms\" => [%{\"id\" => ^room_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"doesn't return a room if it's private\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"search\",\n          %{query: \"foo\"}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"items\" => []},\n        t.client_ws\n      )\n    end\n\n    test \"doesn't return a room if it's private (mixed search)\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"search\",\n          %{query: \"foo\"}\n        )\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"rooms\" => []},\n        t.client_ws\n      )\n    end\n\n    test \"returns user if query matches\", t do\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"search\",\n          %{query: \"@\" <> t.user.username}\n        )\n\n      u_id = t.user.id\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"items\" => [%{\"id\" => ^u_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"returns user if query matches (mixed search)\", t do\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"search\",\n          %{query: \"@\" <> t.user.username}\n        )\n\n      u_id = t.user.id\n\n      WsClient.assert_reply_legacy(\n        ref,\n        %{\"users\" => [%{\"id\" => ^u_id}]},\n        t.client_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_calls/unban_from_room_test.exs",
    "content": "defmodule BrothTest.UnbanFromRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket unban_from_room operation\" do\n    test \"unbans a person\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      %{id: blocked_id} = Factory.create(User)\n\n      Beef.RoomBlocks.insert(%{\n        userId: blocked_id,\n        roomId: room_id,\n        modId: t.user.id\n      })\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n\n      # block the person.\n      ref =\n        WsClient.send_call_legacy(\n          t.client_ws,\n          \"unban_from_room\",\n          %{\"userId\" => blocked_id}\n        )\n\n      WsClient.assert_reply_legacy(ref, %{})\n\n      refute Beef.RoomBlocks.blocked?(room_id, blocked_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/_THIS DIRECTORY TO BE DEPRECATED",
    "content": ""
  },
  {
    "path": "kousa/test/broth/_casts/add_speaker_test.exs",
    "content": "defmodule BrothTest.AddSpeakerTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket add_speaker operation\" do\n    test \"makes the person a speaker\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n      Kousa.Room.set_role(speaker_id, :raised_hand, by: t.user.id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # add the person as a speaker.\n      WsClient.send_msg_legacy(t.client_ws, \"add_speaker\", %{\"userId\" => speaker_id})\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      assert Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n\n    @tag :skip\n    test \"you can't make a person a speaker if you aren't a mod\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/ask_to_speak_test.exs",
    "content": "defmodule BrothTest.AskToSpeakTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket ask_to_speak operation\" do\n    test \"poromotes the person to ask_to_speak\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # make the ask_to_speak request\n      WsClient.send_msg_legacy(speaker_ws, \"ask_to_speak\", %{})\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"hand_raised\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"hand_raised\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      refute Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/auth_test.exs",
    "content": "defmodule BrothTest.AuthTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = %{id: user_id} = Factory.create(User)\n    tokens = Kousa.Utils.TokenUtils.create_tokens(user)\n\n    on_exit(fn ->\n      case Registry.lookup(Onion.UserSessionRegistry, user_id) do\n        [{usersession_pid, _}] ->\n          Process.link(usersession_pid)\n\n        _ ->\n          :ok\n      end\n    end)\n\n    {:ok, tokens: tokens, user_id: user_id}\n  end\n\n  describe \"the websocket auth operation\" do\n    test \"is required within the timeout time or else the connection will be closed\" do\n      # set it to trap exits so we can watch the websocket connection die\n      Process.flag(:trap_exit, true)\n\n      # start and link the websocket client\n      pid = start_supervised!(WsClient)\n      Process.link(pid)\n\n      WsClient.assert_dies(pid, fn -> nil end, :normal)\n    end\n\n    test \"can be sent an auth\", %{tokens: tokens, user_id: user_id} do\n      # start and link the websocket client\n      pid = start_supervised!(WsClient)\n      Process.link(pid)\n      WsClient.forward_frames(pid)\n\n      WsClient.send_msg_legacy(pid, \"auth\", %{\n        \"accessToken\" => tokens.accessToken,\n        \"refreshToken\" => tokens.refreshToken,\n        \"platform\" => \"foo\",\n        \"reconnectToVoice\" => false,\n        \"muted\" => false,\n        \"deafened\" => false\n      })\n\n      WsClient.assert_frame_legacy(\"auth-good\", %{\"user\" => %{\"id\" => ^user_id}})\n    end\n\n    test \"fails auth if the accessToken is borked\" do\n      # start and link the websocket client\n      pid = start_supervised!(WsClient)\n\n      # the websocket should die.\n      WsClient.assert_dies(\n        pid,\n        fn ->\n          WsClient.send_msg_legacy(pid, \"auth\", %{\n            \"accessToken\" => \"foo\",\n            \"refreshToken\" => \"bar\",\n            \"platform\" => \"foo\",\n            \"reconnectToVoice\" => false,\n            \"muted\" => false,\n            \"deafened\" => false\n          })\n        end,\n        {:remote, 4001, \"invalid_authentication\"}\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/ban_from_room_chat_test.exs",
    "content": "defmodule BrothTest.BanFromRoomChatTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket chat_user_banned operation\" do\n    test \"bans the person from the room chat\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      banned = %{id: banned_id} = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      # join the speaker user into the room\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg_legacy(t.client_ws, \"ban_from_room_chat\", %{\"userId\" => banned_id})\n      WsClient.assert_frame_legacy(\"chat_user_banned\", %{\"userId\" => ^banned_id}, t.client_ws)\n      WsClient.assert_frame_legacy(\"chat_user_banned\", %{\"userId\" => ^banned_id}, banned_ws)\n\n      assert Onion.Chat.banned?(room_id, banned_id)\n    end\n\n    @tag :skip\n    test \"a non-mod can't ban someone from room chat\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/ban_test.exs",
    "content": "defmodule BrothTest.BanTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket ban operation\" do\n    test \"doesn't work for not-ben awad\", t do\n      banned = Factory.create(User)\n      WsClientFactory.create_client_for(banned)\n\n      WsClient.send_msg_legacy(t.client_ws, \"ban\", %{\n        \"username\" => banned.username,\n        \"reason\" => \"you're a douche\"\n      })\n\n      WsClient.assert_frame_legacy(\"ban_done\", %{\"worked\" => false})\n    end\n\n    @ben_github_id Application.compile_env!(:kousa, :ben_github_id)\n\n    test \"works for ben awad\", t do\n      t.user\n      |> User.changeset(%{githubId: @ben_github_id})\n      |> Beef.Repo.update!()\n\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      WsClient.send_msg_legacy(t.client_ws, \"ban\", %{\n        \"username\" => banned.username,\n        \"reason\" => \"you're a douche\"\n      })\n\n      WsClient.assert_frame_legacy(\"ban_done\", %{\"worked\" => true})\n\n      # this frame is targetted to the banned user\n      WsClient.assert_frame_legacy(\"banned\", _, banned_ws)\n\n      # check that the user has been updated.\n      assert %{reasonForBan: \"you're a douche\"} = Users.get_by_id(banned.id)\n    end\n\n    test \"will destroy a room if they are the owner\", t do\n      t.user\n      |> User.changeset(%{githubId: @ben_github_id})\n      |> Beef.Repo.update!()\n\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          banned_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      WsClient.send_msg_legacy(t.client_ws, \"ban\", %{\n        \"username\" => banned.username,\n        \"reason\" => \"you're a douche\"\n      })\n\n      WsClient.assert_frame_legacy(\"banned\", _, banned_ws)\n\n      # check that the room is gone.\n      refute Beef.Rooms.get_room_by_id(room_id)\n    end\n\n    test \"will eject a user from a room if they aren't alone\", t do\n      t.user\n      |> User.changeset(%{githubId: @ben_github_id})\n      |> Beef.Repo.update!()\n\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      safe = %{id: safe_id} = Factory.create(User)\n      safe_ws = WsClientFactory.create_client_for(safe)\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          safe_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # join the banned user to the room\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      assert %{peoplePreviewList: [_, _]} = Beef.Rooms.get_room_by_id(room_id)\n\n      WsClient.send_msg_legacy(t.client_ws, \"ban\", %{\n        \"username\" => banned.username,\n        \"reason\" => \"you're a douche\"\n      })\n\n      WsClient.assert_frame_legacy(\"banned\", _, banned_ws)\n\n      # check that the room is still there\n      assert %{\n               peoplePreviewList: [%{id: ^safe_id}]\n             } = Beef.Rooms.get_room_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/block_from_room_test.exs",
    "content": "defmodule BrothTest.BlockFromRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  import KousaTest.Support.Deprecations\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket block_from_room operation\" do\n    test \"blocks that person from a room\", t do\n      room_id = t.room_id\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      blocked = %{id: blocked_id} = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      # join the blocked user into the room\n      WsClient.do_call(blocked_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # block the person.\n      WsClient.send_msg_legacy(t.client_ws, \"block_from_room\", %{\"userId\" => blocked_id})\n\n      WsClient.assert_frame_legacy(\n        \"user_left_room\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^blocked_id},\n        t.client_ws\n      )\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n    end\n  end\n\n  describe \"the websocket block_user_and_from_room operation\" do\n    test \"blocks that person from a room\", t do\n      room_id = t.room_id\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      blocked = %{id: blocked_id} = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      # join the blocked user into the room\n      WsClient.do_call(blocked_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      capture_deprecation(fn ->\n        # block the person.\n        WsClient.send_msg_legacy(t.client_ws, \"block_user_and_from_room\", %{\n          \"userId\" => blocked_id\n        })\n\n        WsClient.assert_frame_legacy(\n          \"user_left_room\",\n          %{\"roomId\" => ^room_id, \"userId\" => ^blocked_id},\n          t.client_ws\n        )\n      end)\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n      assert Beef.UserBlocks.blocked?(t.user.id, blocked_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/change_mod_status_test.exs",
    "content": "defmodule BrothTest.ChangeModStatusTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket change_mod_status operation\" do\n    test \"makes the person a mod\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n      Kousa.Room.set_role(speaker_id, :raised_hand, by: t.user.id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # add the person as a speaker.\n      WsClient.send_msg_legacy(t.client_ws, \"add_speaker\", %{\"userId\" => speaker_id})\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      # make the person a mod\n      WsClient.send_msg_legacy(t.client_ws, \"change_mod_status\", %{\n        \"userId\" => speaker_id,\n        \"value\" => true\n      })\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"mod_changed\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"mod_changed\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      assert Beef.RoomPermissions.get(speaker_id, room_id).isMod\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/change_room_creator_test.exs",
    "content": "defmodule BrothTest.ChangeRoomCreatorTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket change_room_creator operation\" do\n    test \"makes the person a room_creator\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n      Kousa.Room.set_role(speaker_id, :raised_hand, by: t.user.id)\n\n      # add the person as a speaker.\n      WsClient.send_msg_legacy(t.client_ws, \"add_speaker\", %{\"userId\" => speaker_id})\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      # make the person a mod\n      WsClient.send_msg_legacy(t.client_ws, \"change_mod_status\", %{\n        \"userId\" => speaker_id,\n        \"value\" => true\n      })\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"mod_changed\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"mod_changed\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      # make the person a room creator.\n      WsClient.send_msg_legacy(t.client_ws, \"change_room_creator\", %{\n        \"userId\" => speaker_id\n      })\n\n      # NB: we get an extraneous speaker_added message here.\n      WsClient.assert_frame_legacy(\n        \"new_room_creator\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id}\n      )\n\n      assert Beef.Rooms.get_room_by_id(room_id).creatorId == speaker_id\n      assert Process.alive?(t.client_ws)\n    end\n\n    @tag :skip\n    test \"a non-owner can't make someone a room creator\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/delete_room_chat_message_test.exs",
    "content": "defmodule BrothTest.DeleteRoomChatMessageTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user, legacy: true)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket delete_room_chat_message operation\" do\n    test \"sends a message to the room\", t do\n      user_id = t.user.id\n\n      %{\"room\" => %{\"id\" => room_id}} =\n        WsClient.do_call_legacy(\n          t.client_ws,\n          \"create_room\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"privacy\" => \"public\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      listener = %{id: listener_id} = Factory.create(User)\n      listener_ws = WsClientFactory.create_client_for(listener)\n\n      # join the speaker user into the room\n      WsClient.do_call_legacy(listener_ws, \"join_room_and_get_info\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # note that an asynchronous delete request doesn't really have\n      # to make sense to anyone.\n\n      # TODO: double check that the listener-id can't be hijacked\n      # (is it only sent to early-block poor attempts to delete messages?)\n      # maybe we should handle this at the frontend level?\n      msg_id = UUID.uuid4()\n\n      WsClient.send_msg_legacy(t.client_ws, \"delete_room_chat_message\", %{\n        \"messageId\" => msg_id,\n        \"userId\" => listener_id\n      })\n\n      WsClient.assert_frame_legacy(\n        \"message_deleted\",\n        %{\n          \"deleterId\" => ^user_id,\n          \"messageId\" => ^msg_id\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"message_deleted\",\n        %{\n          \"deleterId\" => ^user_id,\n          \"messageId\" => ^msg_id\n        },\n        listener_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/follow_test.exs",
    "content": "defmodule BrothTest.FollowTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the follow operation\" do\n    test \"causes you to follow\", t do\n      followed = Factory.create(User)\n\n      refute Beef.Follows.following_me?(followed.id, t.user.id)\n\n      WsClient.send_call_legacy(t.client_ws, \"follow\", %{\n        \"userId\" => followed.id,\n        \"value\" => true\n      })\n\n      WsClient.assert_frame_legacy(_, _payload)\n\n      assert Beef.Follows.following_me?(followed.id, t.user.id)\n    end\n\n    test \"causes you to to unfollow\", t do\n      followed = Factory.create(User)\n\n      Beef.Follows.insert(%{\n        userId: followed.id,\n        followerId: t.user.id\n      })\n\n      assert Beef.Follows.following_me?(followed.id, t.user.id)\n\n      WsClient.send_call_legacy(t.client_ws, \"follow\", %{\n        \"userId\" => followed.id,\n        \"value\" => false\n      })\n\n      WsClient.assert_frame_legacy(_, _payload)\n\n      refute Beef.Follows.following_me?(followed.id, t.user.id)\n    end\n\n    @tag :skip\n    test \"you can't follow yourself?\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/invite_to_room_test.exs",
    "content": "defmodule BrothTest.InviteToRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket invite_to_room operation\" do\n    test \"invites that person to a room\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a follower user that is logged in.\n      follower = %{id: follower_id} = Factory.create(User)\n      follower_ws = WsClientFactory.create_client_for(follower)\n      Kousa.Follow.follow(follower_id, t.user.id, true)\n\n      WsClient.send_msg_legacy(t.client_ws, \"invite_to_room\", %{\"userId\" => follower_id})\n\n      # note this comes from the follower's client\n      WsClient.assert_frame_legacy(\"invitation_to_room\", %{\"roomId\" => ^room_id}, follower_ws)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/leave_room_test.exs",
    "content": "defmodule BrothTest.LeaveRoomTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias Beef.Rooms\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket leave_room operation\" do\n    test \"deletes the room if they are the only person\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      assert Users.get_by_id(t.user.id).currentRoomId == room_id\n\n      WsClient.send_msg_legacy(t.client_ws, \"leave_room\", %{})\n\n      WsClient.assert_frame_legacy(\"you_left_room\", _)\n\n      refute Users.get_by_id(t.user.id).currentRoomId\n      refute Rooms.get_room_by_id(room_id)\n    end\n\n    test \"removes the person from the room if they aren't the only person\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n\n      assert %{peoplePreviewList: [_]} = Rooms.get_room_by_id(room_id)\n\n      WsClient.do_call(other_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      assert %{peoplePreviewList: [_, _]} = Rooms.get_room_by_id(room_id)\n\n      WsClient.send_msg_legacy(other_ws, \"leave_room\", %{})\n\n      WsClient.assert_frame_legacy(\"you_left_room\", _, other_ws)\n\n      assert %{\n               peoplePreviewList: [\n                 %{id: ^user_id}\n               ]\n             } = Rooms.get_room_by_id(room_id)\n    end\n\n    @tag :skip\n    test \"informs multiple clients that the room has been left\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/make_room_public_test.exs",
    "content": "defmodule BrothTest.MakeRoomPublicTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias Beef.Rooms\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket make_room_public operation\" do\n    test \"makes the room public\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n      # make sure the room is private\n\n      assert Rooms.get_room_by_id(room_id).isPrivate\n\n      WsClient.send_msg_legacy(t.client_ws, \"make_room_public\", %{\"newName\" => \"quux room\"})\n\n      WsClient.assert_frame_legacy(\"room_privacy_change\", %{\n        \"name\" => \"quux room\",\n        \"isPrivate\" => false\n      })\n\n      # make sure the room is actually private\n      assert %{\n               isPrivate: false,\n               name: \"quux room\"\n             } = Rooms.get_room_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/mute_test.exs",
    "content": "defmodule BrothTest.MuteTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket mute call operation\" do\n    test \"can be used to swap state\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      WsClient.send_call_legacy(t.client_ws, \"mute\", %{\"value\" => true})\n\n      # obtain the pseudo-response\n      assert_receive({:text, _, _})\n\n      Process.sleep(100)\n\n      map = Onion.RoomSession.get(room_id, :muteMap)\n\n      assert is_map_key(map, t.user.id)\n\n      WsClient.send_call_legacy(t.client_ws, \"mute\", %{\"value\" => false})\n\n      # obtain the pseudo-response\n      assert_receive({:text, _, _})\n      Process.sleep(100)\n\n      map = Onion.RoomSession.get(room_id, :muteMap)\n      refute is_map_key(map, t.user.id)\n    end\n\n    test \"has no effect on initial value\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      WsClient.send_call_legacy(t.client_ws, \"mute\", %{\"value\" => false})\n\n      # obtain the pseudo-response\n      assert_receive({:text, _, _})\n      assert %{} == Onion.RoomSession.get(room_id, :muteMap)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/send_room_chat_msg_test.exs",
    "content": "defmodule BrothTest.SendRoomChatMsgTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  @moduletag :later\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user, legacy: true)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket send_room_chat operation\" do\n    @text_token [%{\"t\" => \"text\", \"v\" => \"foobar\"}]\n\n    test \"sends a message to the room\", t do\n      user_id = t.user.id\n\n      %{\"room\" => %{\"id\" => room_id}} =\n        WsClient.do_call_legacy(\n          t.client_ws,\n          \"create_room\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a listener user that is logged in.\n      listener = Factory.create(User)\n      listener_ws = WsClientFactory.create_client_for(listener, legacy: true)\n\n      # join the listener user into the room\n      WsClient.do_call_legacy(listener_ws, \"join_room_and_get_info\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg_legacy(t.client_ws, \"send_room_chat_msg\", %{\"tokens\" => @text_token})\n\n      WsClient.assert_frame_legacy(\n        \"new_chat_msg\",\n        %{\n          \"msg\" => %{\n            \"tokens\" => @text_token,\n            \"id\" => _,\n            \"avatarUrl\" => _,\n            \"displayName\" => _,\n            \"username\" => _,\n            \"userId\" => ^user_id,\n            \"sentAt\" => _,\n            \"isWhisper\" => false\n          },\n          \"userId\" => ^user_id\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"new_chat_msg\",\n        %{\n          \"msg\" => %{\n            \"tokens\" => @text_token,\n            \"id\" => _,\n            \"avatarUrl\" => _,\n            \"displayName\" => _,\n            \"username\" => _,\n            \"userId\" => ^user_id,\n            \"sentAt\" => _,\n            \"isWhisper\" => false\n          },\n          \"userId\" => ^user_id\n        },\n        listener_ws\n      )\n    end\n\n    @tag :skip\n    test \"won't send an invalid token over.\"\n\n    test \"can be used to send a whispered message\", t do\n      %{\"room\" => %{\"id\" => room_id}} =\n        WsClient.do_call_legacy(\n          t.client_ws,\n          \"create_room\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that can hear.\n      can_hear = Factory.create(User)\n      can_hear_ws = WsClientFactory.create_client_for(can_hear, legacy: true)\n\n      # create a user that can't hear\n      cant_hear = Factory.create(User)\n      cant_hear_ws = WsClientFactory.create_client_for(cant_hear, legacy: true)\n\n      # join the speaker user into the room\n      WsClient.do_call_legacy(can_hear_ws, \"join_room_and_get_info\", %{\"roomId\" => room_id})\n      WsClient.do_call_legacy(cant_hear_ws, \"join_room_and_get_info\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg_legacy(t.client_ws, \"send_room_chat_msg\", %{\n        \"tokens\" => @text_token,\n        \"whisperedTo\" => [can_hear.id]\n      })\n\n      WsClient.assert_frame_legacy(\n        \"new_chat_msg\",\n        %{\"msg\" => %{\"tokens\" => @text_token, \"isWhisper\" => true}},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"new_chat_msg\",\n        %{\"msg\" => %{\"tokens\" => @text_token, \"isWhisper\" => true}},\n        can_hear_ws\n      )\n\n      WsClient.refute_frame(\n        \"new_chat_msg\",\n        cant_hear_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/set_auto_speaker.exs",
    "content": "defmodule BrothTest.SetAutoSpeakerTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias Beef.Rooms\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket set_auto_speaker operation\" do\n    @tag :skip\n    test \"makes the room be auto_speaker\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/set_listener_test.exs",
    "content": "defmodule BrothTest.SetListenerTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket set_listener operation\" do\n    test \"takes a speaker and turns them into listener\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a speaker user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      Beef.RoomPermissions.set_speaker(t.user.id, room_id, true)\n\n      assert Beef.RoomPermissions.speaker?(t.user.id, room_id)\n\n      WsClient.send_msg_legacy(t.client_ws, \"set_listener\", %{\"userId\" => speaker_id})\n\n      WsClient.assert_frame_legacy(\n        \"speaker_removed\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^speaker_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_removed\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^speaker_id},\n        speaker_ws\n      )\n\n      refute Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n  end\n\n  test \"mods can't move other mods to listeners\", t do\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        t.client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    # make sure the user is in there.\n    assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n    # create a user that is logged in and will be moded.\n    mod1 = %{id: mod1_id} = Factory.create(User)\n    mod1_ws = WsClientFactory.create_client_for(mod1)\n\n    # create another user that is logged in and will be moded.\n    mod2 = %{id: mod2_id} = Factory.create(User)\n    mod2_ws = WsClientFactory.create_client_for(mod2)\n\n    # join the mod1 user into the room\n    WsClient.do_call(mod1_ws, \"room:join\", %{\"roomId\" => room_id})\n    WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n    # join the mod2 user into the room\n    WsClient.do_call(mod2_ws, \"room:join\", %{\"roomId\" => room_id})\n    WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n    # make mod1 user a mod\n    Beef.RoomPermissions.set_is_mod(mod1_id, room_id, true)\n    assert Beef.RoomPermissions.mod?(mod1_id, room_id)\n\n    # make mod2 user a mod\n    Beef.RoomPermissions.set_is_mod(mod2_id, room_id, true)\n    assert Beef.RoomPermissions.mod?(mod2_id, room_id)\n\n    # set mod2 as speaker\n    Beef.RoomPermissions.set_speaker(mod2_id, room_id, true)\n    assert Beef.RoomPermissions.speaker?(mod2_id, room_id)\n\n    # set mod2 as listener using mod1\n    WsClient.send_msg_legacy(mod1_ws, \"set_listener\", %{\"userId\" => mod2_id})\n\n    #mod1 can't move mod2 to listeners\n    refute Beef.RoomPermissions.listener?(mod2_id, room_id)\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/speaking_change_test.exs",
    "content": "defmodule BrothTest.SpeakingChangeTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket speaking_change operation\" do\n    test \"toggles the active speaking state\", t do\n      room_id = t.room_id\n\n      # add a second user to the test\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n      WsClient.do_call(other_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      assert %{} = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      WsClient.send_msg_legacy(\n        t.client_ws,\n        \"speaking_change\",\n        %{\"value\" => true}\n      )\n\n      # both websockets will be informed\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        t.client_ws\n      )\n\n      assert is_map_key(map, t.user.id)\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        other_ws\n      )\n\n      assert is_map_key(map, t.user.id)\n\n      map = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      assert is_map_key(map, t.user.id)\n\n      Process.sleep(100)\n\n      WsClient.send_msg_legacy(\n        t.client_ws,\n        \"speaking_change\",\n        %{\"value\" => false}\n      )\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        t.client_ws\n      )\n\n      refute is_map_key(map, t.user.id)\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        other_ws\n      )\n\n      refute is_map_key(map, t.user.id)\n\n      map = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      refute is_map_key(map, t.user.id)\n    end\n\n    test \"does nothing if it's unset\", t do\n      room_id = t.room_id\n\n      # add a second user to the test\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n      WsClient.do_call(other_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      WsClient.send_msg_legacy(\n        t.client_ws,\n        \"speaking_change\",\n        %{\"value\" => false}\n      )\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map}\n      )\n\n      assert map == %{}\n\n      map = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      assert map == %{}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_casts/unban_from_room_chat_test.exs",
    "content": "defmodule BrothTest.UnbanFromRoomChatTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket unban_from_room_chat operation\" do\n    test \"unbans the person from the room chat\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      banned = %{id: banned_id} = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      # join the speaker user into the room\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg_legacy(t.client_ws, \"ban_from_room_chat\", %{\"userId\" => banned_id})\n      WsClient.assert_frame_legacy(\"chat_user_banned\", %{\"userId\" => ^banned_id}, t.client_ws)\n\n      assert Onion.Chat.banned?(room_id, banned_id)\n\n      WsClient.send_msg_legacy(t.client_ws, \"unban_from_room_chat\", %{\"userId\" => banned_id})\n      WsClient.assert_frame_legacy(\"chat_user_unbanned\", %{\"userId\" => ^banned_id}, t.client_ws)\n\n      refute Onion.Chat.banned?(room_id, banned_id)\n    end\n\n    @tag :skip\n    test \"a non-mod can't ban someone from room chat\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/auth/request_test.exs",
    "content": "defmodule BrothTest.Message.Auth.RequestTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Auth.Request\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a block message\" do\n    test \"it populates userId\" do\n      assert {:ok,\n              %{\n                payload: %Request{\n                  accessToken: \"foo\",\n                  refreshToken: \"bar\",\n                  platform: \"baz\",\n                  reconnectToVoice: false,\n                  muted: false,\n                  deafened: false\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"auth:request\",\n                 \"payload\" => %{\n                   \"accessToken\" => \"foo\",\n                   \"refreshToken\" => \"bar\",\n                   \"platform\" => \"baz\",\n                   \"reconnectToVoice\" => false,\n                   \"muted\" => false,\n                   \"deafened\" => false\n                 },\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Request{\n                  accessToken: \"foo\",\n                  refreshToken: \"bar\",\n                  platform: \"baz\",\n                  reconnectToVoice: false,\n                  muted: false,\n                  deafened: false\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"auth:request\",\n                 \"p\" => %{\n                   \"accessToken\" => \"foo\",\n                   \"refreshToken\" => \"bar\",\n                   \"platform\" => \"baz\",\n                   \"reconnectToVoice\" => false,\n                   \"muted\" => false,\n                   \"deafened\" => false\n                 },\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{accessToken: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"auth:request\",\n                 \"payload\" => %{\n                   \"refreshToken\" => \"bar\",\n                   \"platform\" => \"baz\",\n                   \"reconnectToVoice\" => false,\n                   \"muted\" => false,\n                   \"deafened\" => false\n                 },\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/chat/ban_test.exs",
    "content": "defmodule BrothTest.Message.Chat.BanTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Chat.Ban\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a block message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:ban\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"chat:ban\",\n                 \"p\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:ban\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/chat/delete_msg_test.exs",
    "content": "defmodule BrothTest.Message.Chat.DeleteTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Chat.Delete\n\n  describe \"when you send a delete_msg message\" do\n    test \"it populates userId\" do\n      msg_id = UUID.uuid4()\n      user_id = UUID.uuid4()\n\n      assert {:ok,\n              %{\n                payload: %Delete{\n                  messageId: ^msg_id,\n                  userId: ^user_id\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"messageId\" => msg_id, \"userId\" => user_id}\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Delete{\n                  messageId: ^msg_id,\n                  userId: ^user_id\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"chat:delete\",\n                 \"p\" => %{\"messageId\" => msg_id, \"userId\" => user_id}\n               })\n    end\n\n    test \"messageId must be well-formed\" do\n      id = UUID.uuid4()\n\n      assert {:error, %{errors: %{messageId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"userId\" => id, \"messageId\" => \"aaa\"}\n               })\n\n      assert {:error, %{errors: %{messageId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"userId\" => id, \"messageId\" => %{\"foo\" => \"bar\"}}\n               })\n    end\n\n    test \"userId must be well-formed\" do\n      id = UUID.uuid4()\n\n      assert {:error, %{errors: %{userId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"messageId\" => id, \"userId\" => \"aaa\"}\n               })\n\n      assert {:error, %{errors: %{userId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"messageId\" => id, \"userId\" => %{\"foo\" => \"bar\"}}\n               })\n    end\n\n    test \"messageId required\" do\n      id = UUID.uuid4()\n\n      assert {:error, %{errors: %{messageId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"userId\" => id}\n               })\n    end\n\n    test \"userId required\" do\n      id = UUID.uuid4()\n\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:delete\",\n                 \"payload\" => %{\"messageId\" => id}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/chat/send_test.exs",
    "content": "defmodule BrothTest.Message.Chat.SendTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Types.ChatToken\n  alias Broth.Message.Chat.Send\n\n  describe \"when you send a send_msg message\" do\n    test \"it populates userId\" do\n      assert {:ok,\n              %{\n                payload: %Send{\n                  tokens: [\n                    %ChatToken{\n                      type: :text,\n                      value: \"foobar\"\n                    }\n                  ],\n                  whisperedTo: []\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => [\n                     %{\n                       \"type\" => \"text\",\n                       \"value\" => \"foobar\"\n                     }\n                   ]\n                 }\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Send{\n                  tokens: [\n                    %ChatToken{\n                      type: :text,\n                      value: \"foobar\"\n                    }\n                  ]\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"chat:send_msg\",\n                 \"p\" => %{\n                   \"tokens\" => [\n                     %{\n                       \"type\" => \"text\",\n                       \"value\" => \"foobar\"\n                     }\n                   ]\n                 }\n               })\n    end\n\n    test \"empty list is forbidden\" do\n      assert {:error, %{errors: %{tokens: \"must not be empty\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => []\n                 }\n               })\n    end\n\n    test \"non-lists are forbidden\" do\n      assert {:error, %{errors: %{tokens: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => \"foo\"\n                 }\n               })\n\n      assert {:error, %{errors: %{tokens: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => %{\"foo\" => \"bar\"}\n                 }\n               })\n    end\n\n    @message_character_limit Application.compile_env!(:kousa, :message_character_limit)\n\n    test \"a message that's too long fails\" do\n      too_long_message =\n        List.duplicate(\n          %{\"type\" => \"text\", \"value\" => \"a\"},\n          @message_character_limit + 1\n        )\n\n      assert {:error, %{errors: %{tokens: \"combined length too long\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => too_long_message\n                 }\n               })\n    end\n\n    test \"a message with invalid tokens are forbidden\" do\n      assert {:error, %{errors: %{tokens: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => [\"a\"]\n                 }\n               })\n    end\n  end\n\n  describe \"a send_msg message with a whispered list\" do\n    @tokens [%{\"type\" => \"text\", \"value\" => \"foobar\"}]\n    test \"will populate one uuid into whisperedTo\" do\n      uuid = UUID.uuid4()\n\n      assert {:ok, %{payload: %{whisperedTo: [^uuid]}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => @tokens,\n                   \"whisperedTo\" => [uuid]\n                 }\n               })\n    end\n\n    test \"will populate multiple uuids into whisperedTo\" do\n      uuid1 = UUID.uuid4()\n      uuid2 = UUID.uuid4()\n\n      assert {:ok, %{payload: %{whisperedTo: [^uuid1, ^uuid2]}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => @tokens,\n                   \"whisperedTo\" => [uuid1, uuid2]\n                 }\n               })\n    end\n\n    test \"doesn't populate wrong types\" do\n      assert {:error, %{errors: %{whisperedTo: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => @tokens,\n                   \"whisperedTo\" => \"aaa\"\n                 }\n               })\n\n      assert {:error, %{errors: %{whisperedTo: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => @tokens,\n                   \"whisperedTo\" => %{\"foo\" => \"bar\"}\n                 }\n               })\n\n      assert {:error, %{errors: %{whisperedTo: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:send_msg\",\n                 \"payload\" => %{\n                   \"tokens\" => @tokens,\n                   \"whisperedTo\" => [\"aaa\"]\n                 }\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/chat/unban_test.exs",
    "content": "defmodule BrothTest.Message.Chat.UnbanTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Chat.Unban\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a block message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Unban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:unban\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Unban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"chat:unban\",\n                 \"p\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"chat:unban\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/misc/search_test.exs",
    "content": "defmodule BrothTest.Message.Misc.SearchTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Misc.Search\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send the search message\" do\n    test \"you can specify a query of length 3\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Search{query: \"ben\"},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"misc:search\",\n                 \"payload\" => %{query: \"ben\"},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Search{query: \"ben\"},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"misc:search\",\n                 \"p\" => %{query: \"ben\"},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can't specify query < 3\", %{uuid: uuid} do\n      assert {:error, %{errors: %{query: \"should be at least %{count} character(s)\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"misc:search\",\n                 \"p\" => %{\"query\" => \"be\"},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can't specify query > 100\", %{uuid: uuid} do\n      assert {:error, %{errors: %{query: \"should be at most %{count} character(s)\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"misc:search\",\n                 \"p\" => %{\n                   \"query\" =>\n                     Enum.reduce(1..100, \"\", fn c, acc -> acc <> Integer.to_string(c) end)\n                 },\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can't specify nonstrings for query\", %{uuid: uuid} do\n      assert {:error, %{errors: %{query: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"misc:search\",\n                 \"payload\" => %{\"query\" => 0},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you must specify reference\" do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Misc.Search\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"misc:search\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/ban_test.exs",
    "content": "defmodule BrothTest.Message.Room.BanTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Ban\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a block message\" do\n    test \"it populates userId and shouldBanIp\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid, shouldBanIp: true}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:ban\",\n                 \"payload\" => %{\"userId\" => uuid, \"shouldBanIp\" => true}\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:ban\",\n                 \"p\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"omitting the shouldBanIp defaults to false\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid, shouldBanIp: false}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:ban\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:ban\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/create_scheduled_test.exs",
    "content": "defmodule BrothTest.Message.Room.CreateScheduledTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.CreateScheduled\n\n  setup do\n    future = DateTime.utc_now() |> DateTime.add(1, :second)\n    {:ok, uuid: UUID.uuid4(), future: future}\n  end\n\n  describe \"when you send an create_scheduled message\" do\n    test \"it populates create_scheduled fields\", %{uuid: uuid, future: future} do\n      assert {:ok, %{payload: %CreateScheduled{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"scheduledFor\" => future},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %CreateScheduled{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:create_scheduled\",\n                 \"p\" => %{\"name\" => \"foobar\", \"scheduledFor\" => future},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{future: future} do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.Room.CreateScheduled\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"scheduledFor\" => future}\n               })\n    end\n\n    test \"providing the wrong datatype for name is disallowed\", %{uuid: uuid, future: future} do\n      assert {:error, %{errors: %{name: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => [\"foobar\", \"barbaz\"], \"scheduledFor\" => future},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"omitting the name is disallowed\", %{uuid: uuid, future: future} do\n      assert {:error, %{errors: %{name: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"scheduledFor\" => future},\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when you send an create_scheduled message the scheduledFor field\" do\n    test \"strings are ok\", %{uuid: uuid, future: future} do\n      assert {:ok, %{payload: %CreateScheduled{scheduledFor: ^future}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"scheduledFor\" => \"#{future}\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"providing the wrong datatype is disallowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{scheduledFor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"scheduledFor\" => \"xxxx\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"omitting is disallowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{scheduledFor: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"a time in the past is disallowed\", %{uuid: uuid} do\n      past = DateTime.utc_now() |> DateTime.add(-1, :second)\n\n      assert {:error, %{errors: %{scheduledFor: \"is in the past\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"scheduledFor\" => past},\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when you send an create_scheduled message the description field\" do\n    test \"providing a description is allowed\", %{uuid: uuid, future: future} do\n      assert {:ok, %{payload: %CreateScheduled{description: \"barbaz\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\n                   \"name\" => \"foobar\",\n                   \"scheduledFor\" => future,\n                   \"description\" => \"barbaz\"\n                 },\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"providing the wrong datatype for description is disallowed\", %{\n      uuid: uuid,\n      future: future\n    } do\n      assert {:error, %{errors: %{description: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create_scheduled\",\n                 \"payload\" => %{\n                   \"name\" => \"foobar\",\n                   \"scheduledFor\" => future,\n                   \"description\" => [\"foo\"]\n                 },\n                 \"reference\" => uuid\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/create_test.exs",
    "content": "defmodule BrothTest.Message.Room.CreateTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Create\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an create message to change name\" do\n    test \"it populates create fields\", %{uuid: uuid} do\n      assert {:ok, %{payload: %Create{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"description\" => \"a room\"},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %Create{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:create\",\n                 \"p\" => %{\"name\" => \"foobar\", \"description\" => \"a room\"},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"omitting the reference is not allowed\" do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.Create\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"description\" => \"a room\"}\n               })\n    end\n\n    test \"providing the wrong datatype for name is disallowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{name: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\"name\" => [\"foobar\", \"barbaz\"], \"description\" => \"a room\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"providing the wrong datatype for description is disallowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{description: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"description\" => [\"a\", \"room\"]},\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when you send an create message with non-default privacy\" do\n    test \"it validates\", %{uuid: uuid} do\n      assert {:ok, %{payload: %Create{isPrivate: true}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\n                   \"isPrivate\" => true,\n                   \"name\" => \"foobar\",\n                   \"description\" => \"a room\"\n                 },\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"it fails if it's not a boolean\", %{uuid: uuid} do\n      assert {:error, %{errors: %{isPrivate: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\n                   \"isPrivate\" => \"barbaz\",\n                   \"name\" => \"foobar\",\n                   \"description\" => \"a room\"\n                 },\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when you send an create message with autospeaker set\" do\n    test \"it validates\", %{uuid: uuid} do\n      assert {:error, %{errors: %{autoSpeaker: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:create\",\n                 \"payload\" => %{\n                   \"autoSpeaker\" => \"barbaz\",\n                   \"name\" => \"foobar\",\n                   \"description\" => \"a room\"\n                 },\n                 \"reference\" => uuid\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/deafen_test.exs",
    "content": "defmodule BrothTest.Message.Room.DeafenTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Deafen\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an room:deafen message\" do\n    test \"an empty payload is ok.\", %{uuid: uuid} do\n      assert {:ok, %{payload: %Deafen{deafened: true}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:deafen\",\n                 \"payload\" => %{deafened: true},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %Deafen{deafened: false}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:deafen\",\n                 \"p\" => %{deafened: false},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"deafen parameter is required\", %{uuid: uuid} do\n      assert {:error, %{errors: %{deafened: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:deafen\",\n                 \"payload\" => %{},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"reference is required\" do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.Deafen\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:deafen\",\n                 \"payload\" => %{\"deafened\" => true}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/delete_scheduled_test.exs",
    "content": "defmodule BrothTest.Message.Room.DeleteScheduledTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.DeleteScheduled\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a delete_scheduled message\" do\n    test \"it populates roomId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %DeleteScheduled{roomId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:delete_scheduled\",\n                 \"payload\" => %{\"roomId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %DeleteScheduled{roomId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:delete_scheduled\",\n                 \"p\" => %{\"roomId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.Room.DeleteScheduled\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:delete_scheduled\",\n                 \"payload\" => %{\"roomId\" => uuid}\n               })\n    end\n\n    test \"omitting the roomId is not allowed\" do\n      assert {:error, %{errors: %{roomId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:delete_scheduled\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"roomId must be a UUID\" do\n      assert {:error, %{errors: %{roomId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:delete_scheduled\",\n                 \"payload\" => %{\"roomId\" => \"aaa\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/get_banned_users_test.exs",
    "content": "defmodule BrothTest.Message.Room.GetBannedUsersTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.GetBannedUsers\n\n  @user_id UUID.uuid4()\n  @room_id UUID.uuid4()\n\n  setup do\n    {:ok, uuid: UUID.uuid4(), state: %{data_source: __MODULE__, user_id: @user_id}}\n  end\n\n  def get_current_room_id(_), do: @room_id\n\n  describe \"when you send the get_banned_users message\" do\n    test \"it assumes cursor 0, limit 100\", %{uuid: uuid, state: state} do\n      assert {:ok,\n              %{\n                payload: %GetBannedUsers{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetBannedUsers{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"op\" => \"room:get_banned_users\",\n                   \"p\" => %{},\n                   \"ref\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"you can specify a different cursor value\", %{uuid: uuid, state: state} do\n      assert {:ok,\n              %{\n                payload: %GetBannedUsers{cursor: 10}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{\"cursor\" => 10},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"you can specify a different limit value\", %{uuid: uuid, state: state} do\n      assert {:ok,\n              %{\n                payload: %GetBannedUsers{limit: 50}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{\"limit\" => 50},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"you can't specify a limit == 0\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{limit: \"too low\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{\"limit\" => 0},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"you can't specify nonintegers for cursor or limit\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{limit: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{\"limit\" => \"foo\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      assert {:error, %{errors: %{cursor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{\"cursor\" => \"foo\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"you must specify reference, state: state\", %{state: state} do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.Room.GetBannedUsers\", _}]}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:get_banned_users\",\n                   \"payload\" => %{}\n                 },\n                 state\n               )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/get_info_test.exs",
    "content": "defmodule BrothTest.Message.Room.GetInfoTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.GetInfo\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an leave message\" do\n    test \"an empty payload is ok.\", %{uuid: uuid} do\n      assert {:ok, %{payload: %GetInfo{roomId: ^uuid}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_info\",\n                 \"payload\" => %{roomId: uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %GetInfo{roomId: ^uuid}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:get_info\",\n                 \"p\" => %{roomId: uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"roomId parameter is required\" do\n      assert {:ok, %{payload: %GetInfo{roomId: nil}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_info\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/get_invite_list_test.exs",
    "content": "defmodule BrothTest.Message.Room.GetInviteListTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.GetInviteList\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send the get_invite_list message\" do\n    test \"it assumes cursor 0, limit 100\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetInviteList{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetInviteList{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:get_invite_list\",\n                 \"p\" => %{},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can specify a different cursor value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetInviteList{cursor: 10}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{\"cursor\" => 10},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can specify a different limit value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetInviteList{limit: 50}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{\"limit\" => 50},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify a limit == 0\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"too low\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{\"limit\" => 0},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify nonintegers for cursor or limit\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{\"limit\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n\n      assert {:error, %{errors: %{cursor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{\"cursor\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you must specify reference\" do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.Room.GetInviteList\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_invite_list\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/get_scheduled_test.exs",
    "content": "defmodule BrothTest.Message.Room.GetScheduledTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.GetScheduled\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an get_scheduled message\" do\n    test \"an empty payload is ok.\" do\n      assert {:ok,\n              %{\n                payload: %GetScheduled{\n                  range: \"all\",\n                  userId: nil,\n                  cursor: nil\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_scheduled\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetScheduled{\n                  range: \"all\",\n                  userId: nil,\n                  cursor: nil\n                }\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:get_scheduled\",\n                 \"p\" => %{},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n  end\n\n  describe \"for get_scheduled supplying range parameter\" do\n    test \"supplying all directly is possible\" do\n      assert {:ok, %{payload: %GetScheduled{range: \"all\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_scheduled\",\n                 \"payload\" => %{\"range\" => \"all\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"supplying upcoming is possible\" do\n      assert {:ok, %{payload: %GetScheduled{range: \"upcoming\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_scheduled\",\n                 \"payload\" => %{\"range\" => \"upcoming\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"anything else is fail\" do\n      assert {:error, %{errors: %{range: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_scheduled\",\n                 \"payload\" => %{\"range\" => \"invalid\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\n\n  describe \"for get_scheduled supplying userId parameter\" do\n    test \"supplying is possible\" do\n      uuid = UUID.uuid4()\n\n      assert {:ok, %{payload: %GetScheduled{userId: ^uuid}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_scheduled\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\n\n  describe \"for get_scheduled supplying cursor parameter\" do\n    test \"supplying cursor is possible\" do\n      assert {:ok, %{payload: %GetScheduled{cursor: 10}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_scheduled\",\n                 \"payload\" => %{\"cursor\" => 10},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/get_top_test.exs",
    "content": "defmodule BrothTest.Message.Room.GetTopTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.GetTop\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send the room:get_top message\" do\n    test \"it assumes cursor 0, limit 100\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetTop{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetTop{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:get_top\",\n                 \"p\" => %{},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can specify a different cursor value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetTop{cursor: 10}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{\"cursor\" => 10},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can specify a different limit value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetTop{limit: 50}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{\"limit\" => 50},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify a limit == 0\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"too low\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{\"limit\" => 0},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify nonintegers for cursor or limit\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{\"limit\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n\n      assert {:error, %{errors: %{cursor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{\"cursor\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you must specify reference\" do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.GetTop\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:get_top\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/invite_test.exs",
    "content": "defmodule BrothTest.Message.Room.InviteTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Invite\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an invite message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Invite{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:invite\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Invite{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:invite\",\n                 \"p\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:invite\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/join_test.exs",
    "content": "defmodule BrothTest.Message.Room.JoinTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Join\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a join message\" do\n    test \"it populates roomId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Join{roomId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:join\",\n                 \"payload\" => %{\"roomId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Join{roomId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:join\",\n                 \"p\" => %{\"roomId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.Join\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:join\",\n                 \"payload\" => %{\"roomId\" => uuid}\n               })\n    end\n\n    test \"omitting the roomId is not allowed\" do\n      assert {:error, %{errors: %{roomId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:join\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"roomId must be a UUID\" do\n      assert {:error, %{errors: %{roomId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:join\",\n                 \"payload\" => %{\"roomId\" => \"aaa\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/leave_test.exs",
    "content": "defmodule BrothTest.Message.Room.LeaveTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Leave\n\n  describe \"when you send an leave message\" do\n    test \"an empty payload is ok, but requires ref\" do\n      assert {:ok,\n              %{\n                payload: %Leave{}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:leave\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Leave{}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:leave\",\n                 \"p\" => %{},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/mute_test.exs",
    "content": "defmodule BrothTest.Message.Room.MuteTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.Mute\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an room:mute message\" do\n    test \"an empty payload is ok.\", %{uuid: uuid} do\n      assert {:ok, %{payload: %Mute{muted: true}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:mute\",\n                 \"payload\" => %{muted: true},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %Mute{muted: false}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:mute\",\n                 \"p\" => %{muted: false},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"muted parameter is required\", %{uuid: uuid} do\n      assert {:error, %{errors: %{muted: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:mute\",\n                 \"payload\" => %{},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"reference is required\" do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.Mute\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:mute\",\n                 \"payload\" => %{\"muted\" => true}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/set_active_speaker_test.exs",
    "content": "defmodule BrothTest.Message.Room.SetActiveSpeakerTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Broth.Message.Room.SetActiveSpeaker\n\n  describe \"when you send a room:set_active_speaker message\" do\n    test \"a basic message works\" do\n      assert {:ok, %{payload: %SetActiveSpeaker{active: true}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_active_speaker\",\n                 \"payload\" => %{active: true}\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %SetActiveSpeaker{active: false}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:set_active_speaker\",\n                 \"p\" => %{active: false}\n               })\n    end\n\n    test \"active parameter is required\" do\n      assert {:error, %{errors: %{active: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_active_speaker\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/set_auth_test.exs",
    "content": "defmodule BrothTest.Message.Room.SetAuthTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.SetAuth\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an set mod message\" do\n    test \"it validates\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %SetAuth{userId: ^uuid, level: :mod}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_auth\",\n                 \"payload\" => %{\"userId\" => uuid, \"level\" => \"mod\"}\n               })\n\n      assert {:ok,\n              %{\n                payload: %SetAuth{userId: ^uuid, level: :owner}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_auth\",\n                 \"payload\" => %{\"userId\" => uuid, \"level\" => \"owner\"}\n               })\n\n      assert {:ok,\n              %{\n                payload: %SetAuth{userId: ^uuid, level: :user}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_auth\",\n                 \"payload\" => %{\"userId\" => uuid, \"level\" => \"user\"}\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %SetAuth{userId: ^uuid, level: :mod}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:set_auth\",\n                 \"p\" => %{\"userId\" => uuid, \"level\" => \"mod\"}\n               })\n    end\n\n    test \"omitting the id is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_auth\",\n                 \"payload\" => %{\"level\" => \"mod\"}\n               })\n    end\n\n    test \"omitting level is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{level: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_auth\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"level must be the correct form\", %{uuid: uuid} do\n      assert {:error, %{errors: %{level: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:set_auth\",\n                 \"payload\" => %{\"userId\" => uuid, \"level\" => \"admin\"}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/set_role_test.exs",
    "content": "defmodule BrothTest.Message.Room.SetRoleTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.Room.SetRole\n\n  setup do\n    state = %{user: %{id: UUID.uuid4()}}\n    {:ok, state: state}\n  end\n\n  describe \"when you send an set role message to speaker\" do\n    test \"it validates\", %{state: state} do\n      user_id = state.user.id\n\n      assert {:ok,\n              %{\n                payload: %SetRole{userId: ^user_id, role: :speaker}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:set_role\",\n                   \"payload\" => %{\"userId\" => user_id, \"role\" => \"speaker\"}\n                 },\n                 state\n               )\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %SetRole{userId: ^user_id, role: :speaker}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"op\" => \"room:set_role\",\n                   \"p\" => %{\"userId\" => user_id, \"role\" => \"speaker\"}\n                 },\n                 state\n               )\n    end\n\n    test \"zeroing out the userId is not allowed\", %{state: state} do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:set_role\",\n                   \"payload\" => %{\"role\" => \"speaker\", \"userId\" => nil}\n                 },\n                 state\n               )\n\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:set_role\",\n                   \"payload\" => %{\"role\" => \"speaker\", \"userId\" => \"\"}\n                 },\n                 state\n               )\n    end\n\n    test \"if you don't supply the userId, it's treated as self\", %{state: state} do\n      user_id = state.user.id\n\n      assert {:ok, %{payload: %SetRole{userId: ^user_id}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:set_role\",\n                   \"payload\" => %{\"role\" => \"speaker\"}\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an set role message to\" do\n    test \"raised hand it validates\", %{state: state} do\n      user_id = state.user.id\n\n      assert {:ok,\n              %{\n                payload: %SetRole{userId: ^user_id, role: :raised_hand}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:set_role\",\n                   \"payload\" => %{\"userId\" => user_id, \"role\" => \"raised_hand\"}\n                 },\n                 state\n               )\n    end\n\n    test \"listener it validates\", %{state: state} do\n      user_id = state.user.id\n\n      assert {:ok,\n              %{\n                payload: %SetRole{userId: ^user_id, role: :listener}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:set_role\",\n                   \"payload\" => %{\"userId\" => user_id, \"role\" => \"listener\"}\n                 },\n                 state\n               )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/unban_test.exs",
    "content": "defmodule BrothTest.Message.Room.UnbanTest do\n  use ExUnit.Case, async: true\n  @moduletag :message\n\n  alias Broth.Message.Room.Unban\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a unban message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Unban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:unban\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Unban{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:unban\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.Unban\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:unban\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:unban\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"userId must be a UUID\" do\n      assert {:error, %{errors: %{userId: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:unban\",\n                 \"payload\" => %{\"userId\" => \"aaa\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/update_scheduled_test.exs",
    "content": "defmodule BrothTest.Message.Room.UpdateScheduledTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  @moduletag :message\n\n  alias Beef.Schemas.ScheduledRoom\n  alias Beef.Schemas.User\n  alias Broth.Message.Room.UpdateScheduled\n  alias KousaTest.Support.Factory\n\n  setup do\n    # TODO: make this a mock instead of a db dependency.\n    user = Factory.create(User)\n    room = Factory.create(ScheduledRoom, creatorId: user.id)\n\n    {:ok, uuid: UUID.uuid4(), room: room}\n  end\n\n  describe \"when you send an update message\" do\n    test \"it populates update fields\", %{uuid: uuid, room: room} do\n      assert {:ok, %{payload: %UpdateScheduled{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %UpdateScheduled{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"room:update_scheduled\",\n                 \"p\" => %{\"name\" => \"foobar\", \"id\" => room.id},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{room: room} do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.Room.UpdateScheduled\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"id\" => room.id}\n               })\n    end\n\n    test \"omitting the id is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{id: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"an invalid id is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{id: \"room not found\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"name\" => \"foobar\", \"id\" => UUID.uuid4()},\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when updating scheduled room name\" do\n    test \"providing the wrong datatype for name is disallowed\", %{uuid: uuid, room: room} do\n      assert {:error, %{errors: %{name: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"id\" => room.id, \"name\" => [\"foobar\", \"barbaz\"]},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"erasing the name is disallowed\", %{uuid: uuid, room: room} do\n      assert {:error, %{errors: %{name: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"name\" => nil, \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n\n      assert {:error, %{errors: %{name: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"name\" => \"\", \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when you send an update message to change description\" do\n    test \"it validates\", %{uuid: uuid, room: room} do\n      assert {:ok, %{payload: %UpdateScheduled{description: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"description\" => \"foobar\", \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n    end\n  end\n\n  describe \"when you send an update message to change scheduled time\" do\n    test \"it validates\", %{uuid: uuid, room: room} do\n      time = DateTime.utc_now() |> DateTime.add(1, :second)\n\n      assert {:ok, %{payload: %UpdateScheduled{scheduledFor: ^time}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"scheduledFor\" => to_string(time), \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"it fails if it's not a proper time\", %{uuid: uuid, room: room} do\n      assert {:error, %{errors: %{scheduledFor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"scheduledFor\" => \"aaa\", \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"it fails if it's in the past\", %{uuid: uuid, room: room} do\n      time = DateTime.utc_now() |> DateTime.add(-1, :second)\n\n      assert {:error, %{errors: %{scheduledFor: \"is in the past\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"room:update_scheduled\",\n                 \"payload\" => %{\"scheduledFor\" => to_string(time), \"id\" => room.id},\n                 \"reference\" => uuid\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/room/update_test.exs",
    "content": "defmodule BrothTest.Message.Room.UpdateTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  @moduletag :message\n\n  alias Beef.Schemas.User\n  alias Beef.Schemas.Room\n  alias KousaTest.Support.Factory\n\n  setup do\n    user = Factory.create(User)\n    _room = Factory.create(Room, creatorId: user.id)\n\n    state = %Broth.SocketHandler{user: user}\n\n    {:ok, uuid: UUID.uuid4(), state: state}\n  end\n\n  describe \"when you send an update message\" do\n    test \"it populates update fields\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %Room{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"name\" => \"foobar\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      # short form also allowed\n      assert {:ok, %{payload: %Room{name: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"op\" => \"room:update\",\n                   \"p\" => %{\"name\" => \"foobar\"},\n                   \"ref\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"omitting the reference is not allowed\", %{state: state} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.Room.Update\", _}]}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"name\" => \"foobar\"}\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an update message to change the name\" do\n    test \"providing the wrong datatype for name is disallowed\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{name: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"name\" => [\"foobar\", \"barbaz\"]},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"empty name is disallowed\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{name: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"name\" => nil},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      assert {:error, %{errors: %{name: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"name\" => \"\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an update message to change description\" do\n    test \"it validates\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %Room{description: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"description\" => \"foobar\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an update message to change privacy\" do\n    test \"it validates\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %Room{isPrivate: true}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"isPrivate\" => true},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an update message to change autoSpeaker\" do\n    test \"it validates\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %Room{autoSpeaker: true}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"autoSpeaker\" => true},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an update message to change chatThrottle\" do\n    test \"it validates\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %Room{chatThrottle: 2000}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"chatThrottle\" => 2000},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"providing negative number is not allowed\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{chatThrottle: \"must be greater than or equal to %{number}\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"room:update\",\n                   \"payload\" => %{\"chatThrottle\" => -1},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/admin_update_test.exs",
    "content": "defmodule BrothTest.Message.User.AdminUpdateTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.AdminUpdate\n\n  setup do\n    {:ok, username: UUID.uuid4()}\n  end\n\n  describe \"when you send an admin_update message\" do\n    # test \"it populates username\", %{username: username} do\n    #   assert {:ok,\n    #           %{\n    #             payload: %AdminUpdate{\n    #               username: ^username,\n    #               user: %{\n    #                 staff: true,\n    #                 contributions: 100\n    #               }\n    #             }\n    #           }} =\n    #            BrothTest.Support.Message.validate(%{\n    #              \"operator\" => \"user:admin_update\",\n    #              \"payload\" => %{\n    #                \"username\" => username,\n    #                \"user\" => %{\"staff\" => true, \"contributions\" => 100}\n    #              },\n    #              \"reference\" => UUID.uuid4()\n    #            })\n\n    #   # short form also allowed\n    #   assert {:ok,\n    #           %{\n    #             payload: %AdminUpdate{\n    #               username: ^username,\n    #               user: %{\n    #                 staff: true,\n    #                 contributions: 100\n    #               }\n    #             }\n    #           }} =\n    #            BrothTest.Support.Message.validate(%{\n    #              \"op\" => \"user:admin_update\",\n    #              \"p\" => %{\n    #                \"username\" => username,\n    #                \"user\" => %{\"staff\" => true, \"contributions\" => 100}\n    #              },\n    #              \"ref\" => UUID.uuid4()\n    #            })\n    # end\n\n    # test \"omitting the username is not allowed\" do\n    #   assert {:error, %{errors: %{username: \"can't be blank\"}}} =\n    #            BrothTest.Support.Message.validate(%{\n    #              \"operator\" => \"user:admin_update\",\n    #              \"payload\" => %{\"user\" => %{\"staff\" => true, \"contributions\" => 100}},\n    #              \"reference\" => UUID.uuid4()\n    #            })\n    # end\n\n    # test \"omitting the reference is not allowed\", %{username: username} do\n    #   assert {:error,\n    #           %{errors: [reference: {\"is required for Broth.Message.User.AdminUpdate\", _}]}} =\n    #            BrothTest.Support.Message.validate(%{\n    #              \"operator\" => \"user:admin_update\",\n    #              \"payload\" => %{\n    #                \"username\" => username,\n    #                \"user\" => %{\"staff\" => true, \"contributions\" => 100}\n    #              }\n    #            })\n    # end\n\n    # test \"omitting staff is allowed\", %{username: username} do\n    #   assert {:ok,\n    #           %{\n    #             payload: %AdminUpdate{\n    #               username: ^username,\n    #               user: %{\n    #                 contributions: 100\n    #               }\n    #             }\n    #           }} =\n    #            BrothTest.Support.Message.validate(%{\n    #              \"op\" => \"user:admin_update\",\n    #              \"p\" => %{\n    #                \"username\" => username,\n    #                \"user\" => %{\"contributions\" => 100}\n    #              },\n    #              \"ref\" => UUID.uuid4()\n    #            })\n    # end\n\n    # test \"omitting contributions is allowed\", %{username: username} do\n    #   assert {:ok,\n    #           %{\n    #             payload: %AdminUpdate{\n    #               username: ^username,\n    #               user: %{\n    #                 staff: true\n    #               }\n    #             }\n    #           }} =\n    #            BrothTest.Support.Message.validate(%{\n    #              \"op\" => \"user:admin_update\",\n    #              \"p\" => %{\n    #                \"username\" => username,\n    #                \"user\" => %{\"staff\" => true}\n    #              },\n    #              \"ref\" => UUID.uuid4()\n    #            })\n    # end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/ban_test.exs",
    "content": "defmodule BrothTest.Message.User.BanTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.Ban\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an ban message\" do\n    test \"it populates id\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid, reason: \"foobar\"}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:ban\",\n                 \"payload\" => %{\"userId\" => uuid, \"reason\" => \"foobar\"},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Ban{userId: ^uuid, reason: \"foobar\"}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:ban\",\n                 \"p\" => %{\"userId\" => uuid, \"reason\" => \"foobar\"},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the id is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:ban\",\n                 \"payload\" => %{\"reason\" => \"foobar\"},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reason is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{reason: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:ban\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.Ban\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:ban\",\n                 \"payload\" => %{\"userId\" => uuid, \"reason\" => \"foobar\"}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/block_test.exs",
    "content": "defmodule BrothTest.Message.User.BlockTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.Block\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an ban message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Block{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:block\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Block{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:block\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:block\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.Block\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:block\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/create_bot_test.exs",
    "content": "defmodule BrothTest.Message.User.CreateBotTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.CreateBot\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send create bot\" do\n    test \"it populates create fields\", %{uuid: uuid} do\n      assert {:ok, %{payload: %CreateBot{username: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:create_bot\",\n                 \"payload\" => %{\"username\" => \"foobar\"},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok, %{payload: %CreateBot{username: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:create_bot\",\n                 \"p\" => %{\"username\" => \"foobar\"},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"omitting the reference is not allowed\" do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.CreateBot\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:create_bot\",\n                 \"payload\" => %{\"username\" => \"foobar\"}\n               })\n    end\n\n    test \"providing the wrong datatype for username is disallowed\", %{uuid: uuid} do\n      assert {:error, %{errors: %{username: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:create_bot\",\n                 \"payload\" => %{\"username\" => [\"foobar\", \"barbaz\"]},\n                 \"reference\" => uuid\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/follow_test.exs",
    "content": "defmodule BrothTest.Message.User.FollowTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.Follow\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an follow message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Follow{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:follow\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Follow{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:follow\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:follow\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.Follow\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:follow\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/get_bots_test.exs",
    "content": "defmodule BrothTest.Message.User.GetBotsTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.GetBots\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an get_bots message\" do\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.GetBots\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_bots\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/get_followers_test.exs",
    "content": "defmodule BrothTest.Message.User.GetFollowersTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.GetFollowers\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a get_followers message\" do\n    test \"it assumes cursor 0, limit 100\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetFollowers{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetFollowers{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:get_followers\",\n                 \"p\" => %{},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can specify a different cursor value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetFollowers{cursor: 10}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{\"cursor\" => 10},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can specify a different limit value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetFollowers{limit: 50}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{\"limit\" => 50},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify a limit == 0\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"too low\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{\"limit\" => 0},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify nonintegers for cursor or limit\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{\"limit\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n\n      assert {:error, %{errors: %{cursor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{\"cursor\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you must specify reference\" do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.User.GetFollowers\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_followers\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/get_following_test.exs",
    "content": "defmodule BrothTest.Message.User.GetFollowingTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.GetFollowing\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send a get_following message\" do\n    test \"it assumes cursor 0, limit 100\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetFollowing{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{},\n                 \"reference\" => uuid\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetFollowing{cursor: 0, limit: 100},\n                reference: ^uuid\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:get_following\",\n                 \"p\" => %{},\n                 \"ref\" => uuid\n               })\n    end\n\n    test \"you can specify a different cursor value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetFollowing{cursor: 10}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{\"cursor\" => 10},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can specify a different limit value\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetFollowing{limit: 50}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{\"limit\" => 50},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify a limit == 0\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"too low\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{\"limit\" => 0},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you can't specify nonintegers for cursor or limit\", %{uuid: uuid} do\n      assert {:error, %{errors: %{limit: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{\"limit\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n\n      assert {:error, %{errors: %{cursor: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{\"cursor\" => \"foo\"},\n                 \"reference\" => uuid\n               })\n    end\n\n    test \"you must specify reference\" do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.User.GetFollowing\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_following\",\n                 \"payload\" => %{}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/get_info_test.exs",
    "content": "defmodule BrothTest.Message.User.GetInfoTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.GetInfo\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an get_info message\" do\n    test \"you can supply the user id\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetInfo{userIdOrUsername: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:get_info\",\n                   \"payload\" => %{\"userIdOrUsername\" => uuid},\n                   \"reference\" => UUID.uuid4()\n                 },\n                 %{user_id: UUID.uuid4()}\n               )\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetInfo{userIdOrUsername: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"op\" => \"user:get_info\",\n                   \"p\" => %{\"userIdOrUsername\" => uuid},\n                   \"ref\" => UUID.uuid4()\n                 },\n                 %{user_id: UUID.uuid4()}\n               )\n    end\n\n    # @todo\n    # test \"an empty payload is ok, it assumes self.\" do\n    #   uuid = UUID.uuid4()\n    #   state = %{user_id: uuid}\n\n    #   assert {:ok, %{payload: %GetInfo{userIdOrUsername: ^uuid}}} =\n    #            BrothTest.Support.Message.validate(\n    #              %{\n    #                \"operator\" => \"user:get_info\",\n    #                \"payload\" => %{},\n    #                \"reference\" => UUID.uuid4()\n    #              },\n    #              state\n    #            )\n    # end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/get_relationship_test.exs",
    "content": "defmodule BrothTest.Message.User.GetRelationshipTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.GetRelationship\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an get_relationship message\" do\n    test \"an empty payload is ok.\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %GetRelationship{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_relationship\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %GetRelationship{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:get_relationship\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"userId parameter is required\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:get_relationship\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/revoke_api_key_test.exs",
    "content": "defmodule BrothTest.Message.User.RevokeApiKeyTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.RevokeApiKey\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an revoke_api_key message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %RevokeApiKey{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:revoke_api_key\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %RevokeApiKey{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:revoke_api_key\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:revoke_api_key\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error,\n              %{errors: [reference: {\"is required for Broth.Message.User.RevokeApiKey\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:revoke_api_key\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/unblock_test.exs",
    "content": "defmodule BrothTest.Message.User.UnblockTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.Unblock\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an ban message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Unblock{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:unblock\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Unblock{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:unblock\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:unblock\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.Unblock\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:unblock\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/unfollow_test.exs",
    "content": "defmodule BrothTest.Message.User.UnfollowTest do\n  use ExUnit.Case, async: true\n\n  @moduletag :message\n\n  alias Broth.Message.User.Unfollow\n\n  setup do\n    {:ok, uuid: UUID.uuid4()}\n  end\n\n  describe \"when you send an unfollow message\" do\n    test \"it populates userId\", %{uuid: uuid} do\n      assert {:ok,\n              %{\n                payload: %Unfollow{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:unfollow\",\n                 \"payload\" => %{\"userId\" => uuid},\n                 \"reference\" => UUID.uuid4()\n               })\n\n      # short form also allowed\n      assert {:ok,\n              %{\n                payload: %Unfollow{userId: ^uuid}\n              }} =\n               BrothTest.Support.Message.validate(%{\n                 \"op\" => \"user:unfollow\",\n                 \"p\" => %{\"userId\" => uuid},\n                 \"ref\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the userId is not allowed\" do\n      assert {:error, %{errors: %{userId: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:unfollow\",\n                 \"payload\" => %{},\n                 \"reference\" => UUID.uuid4()\n               })\n    end\n\n    test \"omitting the reference is not allowed\", %{uuid: uuid} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.Unfollow\", _}]}} =\n               BrothTest.Support.Message.validate(%{\n                 \"operator\" => \"user:unfollow\",\n                 \"payload\" => %{\"userId\" => uuid}\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_message/user/update_test.exs",
    "content": "defmodule BrothTest.Message.User.UpdateTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  @moduletag :message\n\n  alias Beef.Schemas.User\n  alias KousaTest.Support.Factory\n\n  setup do\n    # this \"UNIT\" test requires the db because the message gets\n    # initialized off of information in the database.\n    user = Factory.create(User)\n    state = %Broth.SocketHandler{user: user}\n    {:ok, uuid: UUID.uuid4(), state: state}\n  end\n\n  describe \"when you send an update message to change muted state\" do\n    test \"it populates update fields\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %User{muted: true, deafened: true}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"muted\" => true, \"deafened\" => true},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      # short form also allowed\n      assert {:ok, %{payload: %User{muted: false, deafened: false}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"op\" => \"user:update\",\n                   \"p\" => %{\"muted\" => false, \"deafened\" => false},\n                   \"ref\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"omitting the reference is not allowed\", %{state: state} do\n      assert {:error, %{errors: [reference: {\"is required for Broth.Message.User.Update\", _}]}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"muted\" => true, \"deafened\" => true}\n                 },\n                 state\n               )\n    end\n\n    test \"providing the wrong datatype for muted state is disallowed\",\n         %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{muted: \"is invalid\", deafened: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"muted\" => \"foobar\", \"deafened\" => \"barfoo\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      assert {:error, %{errors: %{muted: \"is invalid\", deafened: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\n                     \"muted\" => [\"foobar\", \"barbaz\"],\n                     \"deafened\" => [\"foobar\", \"barbaz\"]\n                   },\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\n\n  describe \"when you send an update message to change the username\" do\n    test \"it populates update fields\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %User{username: \"foobar\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"username\" => \"foobar\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"it accepts good key string for whisperPrivacySetting\", %{uuid: uuid, state: state} do\n      assert {:ok, %{payload: %User{whisperPrivacySetting: :off}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"whisperPrivacySetting\" => \"off\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"it rejects bad key for whisperPrivacySetting\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{whisperPrivacySetting: \"is invalid\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"whisperPrivacySetting\" => \"pancake\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n\n    test \"it rejects attempting to delete the username\", %{uuid: uuid, state: state} do\n      assert {:error, %{errors: %{username: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"username\" => \"\"},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n\n      assert {:error, %{errors: %{username: \"can't be blank\"}}} =\n               BrothTest.Support.Message.validate(\n                 %{\n                   \"operator\" => \"user:update\",\n                   \"payload\" => %{\"username\" => nil},\n                   \"reference\" => uuid\n                 },\n                 state\n               )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_routes/bot_auth_test.exs",
    "content": "defmodule BrothTest.Routes.BotAuthTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias KousaTest.Support.Factory\n  alias BrothTest.HttpRequest\n\n  setup do\n    user = Factory.create(User)\n\n    {:ok, user: user}\n  end\n\n  describe \"the post /bot/auth route\" do\n    test \"trade apiKey for access and refresh tokens\", t do\n      bot = Factory.create(User, apiKey: UUID.uuid4(), botOwnerId: t.user.id)\n\n      {:ok, resp_body} = HttpRequest.post(\"/bot/auth\", %{apiKey: bot.apiKey})\n\n      assert is_binary(resp_body[\"accessToken\"])\n      assert is_binary(resp_body[\"refreshToken\"])\n    end\n\n    test \"banned bot accounts can't get tokens\", t do\n      bot =\n        Factory.create(User, apiKey: UUID.uuid4(), botOwnerId: t.user.id, reasonForBan: \"test ban\")\n\n      assert {:ok, %Finch.Response{status: 400}} =\n               HttpRequest.post(\"/bot/auth\", %{apiKey: bot.apiKey})\n    end\n\n    test \"gets rate limited on X bad requests\", t do\n      bot = Factory.create(User, apiKey: UUID.uuid4(), botOwnerId: t.user.id)\n      key = \"test-rl\"\n\n      Enum.each(0..5, fn _ ->\n        HttpRequest.post(\"/bot/auth\", %{apiKey: UUID.uuid4()}, [{\"rate-limit-key\", key}])\n      end)\n\n      assert {:ok, %Finch.Response{status: 429}} =\n               HttpRequest.post(\"/bot/auth\", %{apiKey: bot.apiKey}, [{\"rate-limit-key\", key}])\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/_types/chat_token.exs",
    "content": "defmodule BrothTest.Message.Types.ChatToken do\n  use ExUnit.Case, async: true\n\n  alias Broth.Message.Types.ChatToken\n  alias Ecto.Changeset\n\n  defp validate(map) do\n    %ChatToken{}\n    |> ChatToken.changeset(map)\n    |> Changeset.apply_action(:validate)\n  end\n\n  @message_character_limit Application.compile_env(:kousa, :message_character_limit)\n\n  describe \"a chat token which is of type text\" do\n    test \"can be validated\" do\n      assert {:ok,\n              %ChatToken{\n                type: :text,\n                value: \"foobar\"\n              }} = validate(%{\"type\" => \"text\", \"value\" => \"foobar\"})\n    end\n\n    test \"short forms work too\" do\n      assert {:ok,\n              %ChatToken{\n                type: :text,\n                value: \"foobar\"\n              }} = validate(%{\"t\" => \"text\", \"v\" => \"foobar\"})\n    end\n\n    test \"is invalid if it has zero length\" do\n      assert {:error, %{errors: [value: {\"can't be blank\", _}]}} =\n               validate(%{\"type\" => \"text\", \"value\" => \"\"})\n    end\n\n    test \"is invalid if it exceeds the message character limit\" do\n      too_many_as =\n        ?a\n        |> List.duplicate(@message_character_limit + 1)\n        |> List.to_string()\n\n      assert {:error, %{errors: [value: {\"should be at most\" <> _, _}]}} =\n               validate(%{\"type\" => \"text\", \"value\" => too_many_as})\n    end\n  end\n\n  describe \"a chat token which is of type mention\" do\n    test \"can be validated\" do\n      assert {:ok,\n              %ChatToken{\n                type: :mention,\n                value: \"foobar\"\n              }} = validate(%{\"type\" => \"mention\", \"value\" => \"foobar\"})\n    end\n  end\n\n  describe \"a chat token which is of type emote\" do\n    test \"can be validated\" do\n      assert {:ok,\n              %ChatToken{\n                type: :emote,\n                value: \"foobar\"\n              }} = validate(%{\"type\" => \"emote\", \"value\" => \"foobar\"})\n    end\n  end\n\n  describe \"a chat token which is of type block\" do\n    test \"can be validated\" do\n      assert {:ok,\n              %ChatToken{\n                type: :block,\n                value: \"this is a code block\"\n              }} = validate(%{\"type\" => \"block\", \"value\" => \"this is a code block\"})\n    end\n  end\n\n  describe \"a chat token which is of type link\" do\n    test \"is valid if it's an https link\" do\n      assert {:ok,\n              %ChatToken{\n                type: :link,\n                value: \"https://github.com\"\n              }} = validate(%{\"type\" => \"link\", \"value\" => \"https://github.com\"})\n    end\n\n    test \"is valid if it's an http link\" do\n      assert {:ok,\n              %ChatToken{\n                type: :link,\n                value: \"http://github.com\"\n              }} = validate(%{\"type\" => \"link\", \"value\" => \"http://github.com\"})\n    end\n\n    test \"is invalid if it's an unspported scheme\" do\n      assert {:error, %{errors: [value: {\"invalid url\", _}]}} =\n               validate(%{\"type\" => \"link\", \"value\" => \"file://home/foo/bar\"})\n    end\n\n    test \"is invalid if it doesn't look like a uri at all\" do\n      assert {:error, %{errors: [value: {\"invalid url\", _}]}} =\n               validate(%{\"type\" => \"link\", \"value\" => \"foobar\"})\n    end\n  end\n\n  describe \"a chat token which is of type emoji\" do\n    test \"can be validated\" do\n      assert {:ok,\n              %ChatToken{\n                type: :emoji,\n                value: \"🤔\"\n              }} = validate(%{\"type\" => \"emoji\", \"value\" => \"🤔\"})\n    end\n  end\n\n  describe \"a chat token which is missing the field\" do\n    test \"type is invalid\" do\n      assert {:error, %{errors: [type: {\"can't be blank\", _}]}} = validate(%{\"value\" => \"foobar\"})\n    end\n\n    test \"value is invalid\" do\n      assert {:error, %{errors: [value: {\"can't be blank\", _}]}} = validate(%{\"type\" => \"text\"})\n    end\n  end\n\n  describe \"a chat token with an invalid\" do\n    test \"type is invalid\" do\n      assert {:error, %{errors: [type: {\"is invalid\", _}]}} =\n               validate(%{\"type\" => \"quux\", \"value\" => \"foobar\"})\n    end\n\n    test \"value is invalid\" do\n      assert {:error, %{errors: [value: {\"is invalid\", _}]}} =\n               validate(%{\"type\" => \"text\", \"value\" => %{\"foo\" => \"bar\"}})\n    end\n  end\n\n  describe \"a chat token encodes\" do\n    test \"to the short form\" do\n      uuid = UUID.uuid4()\n\n      assert Jason.encode!(\n               %ChatToken{\n                 type: :text,\n                 value: \"foobar\"\n               },\n               pretty: true\n             ) ==\n               String.trim(\"\"\"\n               {\n                 \"t\": \"text\",\n                 \"v\": \"foobar\"\n               }\n               \"\"\")\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/auth/request_test.exs",
    "content": "defmodule BrothTest.Auth.RequestTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = %{id: user_id} = Factory.create(User)\n    tokens = Kousa.Utils.TokenUtils.create_tokens(user)\n\n    on_exit(fn ->\n      case Registry.lookup(Onion.UserSessionRegistry, user_id) do\n        [{usersession_pid, _}] ->\n          Process.link(usersession_pid)\n\n        _ ->\n          :ok\n      end\n    end)\n\n    {:ok, tokens: tokens, user_id: user_id}\n  end\n\n  describe \"the websocket auth:request operation\" do\n    test \"is required within the timeout time or else the connection will be closed\" do\n      # set it to trap exits so we can watch the websocket connection die\n      Process.flag(:trap_exit, true)\n\n      # start and link the websocket client\n      pid = start_supervised!(WsClient)\n      Process.link(pid)\n\n      WsClient.assert_dies(pid, fn -> nil end, :normal)\n    end\n\n    test \"can be sent an auth\", %{tokens: tokens, user_id: user_id} do\n      # start and link the websocket client\n      pid = start_supervised!(WsClient)\n      Process.link(pid)\n      WsClient.forward_frames(pid)\n\n      ref =\n        WsClient.send_call(pid, \"auth:request\", %{\n          \"accessToken\" => tokens.accessToken,\n          \"refreshToken\" => tokens.refreshToken,\n          \"platform\" => \"foo\",\n          \"reconnectToVoice\" => false,\n          \"muted\" => false,\n          \"deafen\" => false\n        })\n\n      WsClient.assert_reply(\"auth:request:reply\", ref, %{\"id\" => ^user_id})\n    end\n\n    test \"no deafen\", %{tokens: tokens, user_id: user_id} do\n      # start and link the websocket client\n      client_ws = start_supervised!(WsClient)\n      Process.link(client_ws)\n      WsClient.forward_frames(client_ws)\n\n      # login\n      ref =\n        WsClient.send_call(client_ws, \"auth:request\", %{\n          \"accessToken\" => tokens.accessToken,\n          \"refreshToken\" => tokens.refreshToken,\n          \"platform\" => \"foo\",\n          \"reconnectToVoice\" => false,\n          \"muted\" => false,\n          \"deafen\" => false\n        })\n\n      WsClient.assert_reply(\"auth:request:reply\", ref, %{\"id\" => ^user_id})\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref2 =\n        WsClient.send_call(client_ws, \"auth:request\", %{\n          \"accessToken\" => tokens.accessToken,\n          \"refreshToken\" => tokens.refreshToken,\n          \"platform\" => \"foo\",\n          \"reconnectToVoice\" => false,\n          \"muted\" => false\n        })\n\n      WsClient.assert_reply(\"auth:request:reply\", ref2, %{\"id\" => ^user_id})\n    end\n\n    test \"fails auth if the accessToken is borked\" do\n      # start and link the websocket client\n      client_ws = start_supervised!(WsClient)\n\n      # the websocket should die.\n      WsClient.assert_dies(\n        client_ws,\n        fn ->\n          WsClient.send_call(client_ws, \"auth:request\", %{\n            \"accessToken\" => \"foo\",\n            \"refreshToken\" => \"bar\",\n            \"platform\" => \"foo\",\n            \"reconnectToVoice\" => false,\n            \"muted\" => false,\n            \"deafen\" => false\n          })\n        end,\n        {:remote, 4001, \"invalid_authentication\"}\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/chat/ban_test.exs",
    "content": "defmodule BrothTest.Chat.BanTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket chat:ban operation\" do\n    test \"bans the person from the room chat\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      banned = %{id: banned_id} = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      # join the speaker user into the room\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg(t.client_ws, \"chat:ban\", %{\"userId\" => banned_id})\n      WsClient.assert_frame_legacy(\"chat_user_banned\", %{\"userId\" => ^banned_id}, t.client_ws)\n      WsClient.assert_frame_legacy(\"chat_user_banned\", %{\"userId\" => ^banned_id}, banned_ws)\n\n      assert Onion.Chat.banned?(room_id, banned_id)\n    end\n\n    @tag :skip\n    test \"a non-mod can't ban someone from room chat\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/chat/delete_test.exs",
    "content": "defmodule BrothTest.Chat.DeleteTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket chat:delete operation\" do\n    test \"sends a message to the room\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      listener = %{id: listener_id} = Factory.create(User)\n      listener_ws = WsClientFactory.create_client_for(listener)\n\n      # join the speaker user into the room\n      WsClient.do_call(listener_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # note that an asynchronous delete request doesn't really have\n      # to make sense to anyone.\n      msg_id = UUID.uuid4()\n\n      WsClient.send_msg(t.client_ws, \"chat:delete\", %{\n        \"messageId\" => msg_id,\n        \"userId\" => listener_id\n      })\n\n      WsClient.assert_frame(\n        \"chat:delete\",\n        %{\n          \"deleterId\" => ^user_id,\n          \"messageId\" => ^msg_id,\n          \"userId\" => ^listener_id\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame(\n        \"chat:delete\",\n        %{\n          \"deleterId\" => ^user_id,\n          \"messageId\" => ^msg_id,\n          \"userId\" => ^listener_id\n        },\n        listener_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/chat/send_test.exs",
    "content": "defmodule BrothTest.Chat.SendTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    # first, create a room owned by the primary user.\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket chat:send_msg operation\" do\n    @text_token [%{\"t\" => \"text\", \"v\" => \"foobar\"}]\n\n    test \"sends a message to the room\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n      # create a user that is logged in.\n      listener = Factory.create(User)\n      listener_ws = WsClientFactory.create_client_for(listener)\n\n      WsClient.do_call(listener_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^user_id,\n          \"id\" => msg_uuid,\n          \"isWhisper\" => false\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^user_id,\n          \"id\" => ^msg_uuid,\n          \"isWhisper\" => false\n        },\n        listener_ws\n      )\n    end\n\n    test \"can be used to send a whispered message\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n      # create a user that won't be able to hear\n      cant_hear = Factory.create(User)\n      cant_hear_ws = WsClientFactory.create_client_for(cant_hear)\n\n      # create a user that will be able to hear.\n      can_hear = Factory.create(User)\n      can_hear_ws = WsClientFactory.create_client_for(can_hear)\n\n      # join the two users into the room\n      WsClient.do_call(cant_hear_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.do_call(can_hear_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _, t.client_ws)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _, t.client_ws)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _, cant_hear_ws)\n\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\n        \"tokens\" => @text_token,\n        \"whisperedTo\" => [can_hear.id]\n      })\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"from\" => ^user_id,\n          \"sentAt\" => _,\n          \"id\" => msg_id,\n          \"isWhisper\" => true\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"from\" => ^user_id,\n          \"sentAt\" => _,\n          \"id\" => ^msg_id,\n          \"isWhisper\" => true\n        },\n        can_hear_ws\n      )\n\n      WsClient.refute_frame(\"chat:send\", cant_hear_ws)\n    end\n  end\n\n  describe \"the sender should not be able to send\" do\n    test \"if they have been chat banned from the room\", t do\n      room_id = t.room_id\n\n      # create a user that is logged in.\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # ban the new user\n      WsClient.send_msg(t.client_ws, \"chat:ban\", %{\"userId\" => banned.id})\n\n      WsClient.assert_frame_legacy(\"chat_user_banned\", _)\n      WsClient.assert_frame_legacy(\"chat_user_banned\", _)\n\n      WsClient.send_msg(banned_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n      WsClient.refute_frame(\"chat:send\", banned_ws)\n    end\n\n    test \"if the room chat is disabled by the owner\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n      # create a user that will send message\n      sender = Factory.create(User)\n      sender_ws = WsClientFactory.create_client_for(sender)\n\n      # join the user into room\n      WsClient.do_call(sender_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # disable room chat\n      WsClient.do_call(t.client_ws, \"room:update\", %{\"chatMode\" => \"disabled\"})\n\n      # send chat msg via sender\n      WsClient.send_msg(\n        sender_ws,\n        \"chat:send_msg\",\n        %{\"tokens\" => @text_token}\n      )\n\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n      WsClient.refute_frame(\"chat:send\", sender_ws)\n    end\n  end\n\n  describe \"user should not be able to receive message\" do\n    test \"block, unblock still receives message\", t do\n      room_id = t.room_id\n\n      # create a user that is logged in.\n      blocked = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      WsClient.do_call(blocked_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # block the new user\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => blocked.id})\n      WsClient.do_call(t.client_ws, \"user:unblock\", %{\"userId\" => blocked.id})\n\n      WsClient.send_msg(\n        blocked_ws,\n        \"chat:send_msg\",\n        %{\"tokens\" => @text_token}\n      )\n\n      blocked_id = blocked.id\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^blocked_id,\n          \"id\" => msg_uuid,\n          \"isWhisper\" => false\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^blocked_id,\n          \"id\" => ^msg_uuid,\n          \"isWhisper\" => false\n        },\n        blocked_ws\n      )\n    end\n\n    test \"if they have been blocked by the user\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n      # create a user that is logged in.\n      blocked = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      WsClient.do_call(blocked_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # block the new user\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => blocked.id})\n\n      WsClient.send_msg(\n        blocked_ws,\n        \"chat:send_msg\",\n        %{\"tokens\" => @text_token, \"whisperedTo\" => [user_id]}\n      )\n\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n      # you will still get the message yourself.\n      WsClient.assert_frame(\"chat:send\", _, blocked_ws)\n\n      # new user to avoid 1 sec throttle\n\n      # create a user that is logged in.\n      blocked2 = Factory.create(User)\n      blocked2_ws = WsClientFactory.create_client_for(blocked2)\n\n      WsClient.do_call(blocked2_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # block the new user\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => blocked2.id})\n\n      WsClient.send_msg(\n        blocked2_ws,\n        \"chat:send_msg\",\n        %{\"tokens\" => @text_token}\n      )\n\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n      # you will still get the message yourself.\n      WsClient.assert_frame(\"chat:send\", _, blocked2_ws)\n    end\n\n    test \"if they have been banned from the room\", t do\n      room_id = t.room_id\n      user_id = t.user.id\n\n      # create a user that is logged in.\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # ban the new user\n      WsClient.send_msg(t.client_ws, \"room:ban\", %{\"userId\" => banned.id})\n\n      WsClient.assert_frame_legacy(\"user_left_room\", _)\n\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^user_id,\n          \"isWhisper\" => false\n        },\n        t.client_ws\n      )\n\n      WsClient.refute_frame(\"chat:send\", banned_ws)\n    end\n\n    test \"if whispers are off\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n      # create a user that won't be able to hear\n      cant_hear = Factory.create(User)\n      cant_hear_ws = WsClientFactory.create_client_for(cant_hear)\n\n      WsClient.do_call(cant_hear_ws, \"user:update\", %{\"whisperPrivacySetting\" => \"off\"})\n\n      # create a user that will be able to hear.\n      can_hear = Factory.create(User)\n      can_hear_ws = WsClientFactory.create_client_for(can_hear)\n\n      # join the two users into the room\n      WsClient.do_call(cant_hear_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.do_call(can_hear_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _, t.client_ws)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _, t.client_ws)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _, cant_hear_ws)\n\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\n        \"tokens\" => @text_token,\n        \"whisperedTo\" => [can_hear.id, cant_hear.id]\n      })\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"from\" => ^user_id,\n          \"sentAt\" => _,\n          \"id\" => msg_id,\n          \"isWhisper\" => true\n        },\n        t.client_ws\n      )\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"from\" => ^user_id,\n          \"sentAt\" => _,\n          \"id\" => ^msg_id,\n          \"isWhisper\" => true\n        },\n        can_hear_ws\n      )\n\n      WsClient.refute_frame(\"chat:send\", cant_hear_ws)\n    end\n  end\n\n  describe \"when throttled\" do\n    test \"message not sent\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n\n      WsClient.do_call(t.client_ws, \"room:update\", %{\n        \"chatThrottle\" => 50\n      })\n\n      # create a user that is logged in.\n      listener = Factory.create(User)\n      listener_ws = WsClientFactory.create_client_for(listener)\n\n      WsClient.do_call(listener_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # send the first message\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      # verify that first message is received\n      WsClient.assert_frame(\n        \"chat:send\",\n        _,\n        t.client_ws\n      )\n      WsClient.assert_frame(\n        \"chat:send\",\n        _,\n        listener_ws\n      )\n\n\n      # send second message before the throttle limit is finished, half time of total throttle\n      Process.sleep(25)\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n      # this message should be throttled\n      WsClient.refute_frame(\n        \"chat:send\",\n        t.client_ws\n      )\n      WsClient.refute_frame(\n        \"chat:send\",\n        listener_ws\n      )\n\n      # this message should not be throttled\n      Process.sleep(51)\n      WsClient.send_msg(t.client_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n      WsClient.assert_frame(\n        \"chat:send\",\n        _,\n        t.client_ws\n      )\n      WsClient.assert_frame(\n        \"chat:send\",\n        _,\n        listener_ws\n      )\n\n    end\n  end\n\n  describe \"when follower mode\" do\n    test \"if they are not following they can't send a msg\", t do\n      room_id = t.room_id\n      # create a user that is logged in.\n      non_follower = Factory.create(User)\n      non_follower_ws = WsClientFactory.create_client_for(non_follower)\n\n      WsClient.do_call(t.client_ws, \"room:update\", %{\n        \"chatMode\" => \"follower_only\"\n      })\n\n      WsClient.do_call(non_follower_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg(non_follower_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n      WsClient.refute_frame(\"chat:send\", non_follower_ws)\n    end\n\n    test \"if I become a speaker later I can send msg\", t do\n      room_id = t.room_id\n\n      speaker = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.do_call(t.client_ws, \"room:update\", %{\n        \"chatMode\" => \"follower_only\"\n      })\n\n      WsClient.send_msg(speaker_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n\n      Kousa.Room.set_role(speaker.id, :raised_hand, by: speaker.id)\n      Kousa.Room.set_role(speaker.id, :speaker, by: t.user.id)\n      WsClient.send_msg(speaker_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      speaker_id = speaker.id\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^speaker_id,\n          \"id\" => msg_uuid,\n          \"isWhisper\" => false\n        },\n        t.client_ws\n      )\n    end\n\n    test \"if I become a mod later I can send msg\", t do\n      room_id = t.room_id\n\n      mod = Factory.create(User)\n      mod_ws = WsClientFactory.create_client_for(mod)\n      WsClient.do_call(mod_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.do_call(t.client_ws, \"room:update\", %{\n        \"chatMode\" => \"follower_only\"\n      })\n\n      WsClient.send_msg(mod_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n      WsClient.refute_frame(\"chat:send\", t.client_ws)\n\n      Kousa.Room.set_auth(mod.id, :mod, by: t.user.id)\n      WsClient.send_msg(mod_ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n      mod_id = mod.id\n\n      WsClient.assert_frame(\n        \"chat:send\",\n        %{\n          \"tokens\" => @text_token,\n          \"sentAt\" => _,\n          \"from\" => ^mod_id,\n          \"id\" => msg_uuid,\n          \"isWhisper\" => false\n        },\n        t.client_ws\n      )\n    end\n\n    test \"if I am a follower, mod, speaker, or creator I can send msg\", t do\n      room_id = t.room_id\n      # create a user that is logged in.\n      follower = Factory.create(User)\n      follower_ws = WsClientFactory.create_client_for(follower)\n\n      WsClient.do_call(follower_ws, \"user:follow\", %{\n        \"userId\" => t.user.id\n      })\n\n      WsClient.do_call(follower_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      mod = Factory.create(User)\n      mod_ws = WsClientFactory.create_client_for(mod)\n      WsClient.do_call(mod_ws, \"room:join\", %{\"roomId\" => room_id})\n      Kousa.Room.set_auth(mod.id, :mod, by: t.user.id)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      speaker = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      Kousa.Room.set_role(speaker.id, :raised_hand, by: speaker.id)\n      Kousa.Room.set_role(speaker.id, :speaker, by: t.user.id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.do_call(t.client_ws, \"room:update\", %{\n        \"chatMode\" => \"follower_only\"\n      })\n\n      follower_id = follower.id\n\n      Enum.each(\n        [\n          {\"follower\", follower.id, follower_ws},\n          {\"mod\", mod.id, mod_ws},\n          {\"speaker\", speaker.id, speaker_ws},\n          {\"room creator\", t.user.id, t.client_ws}\n        ],\n        fn {label, id, ws} ->\n          WsClient.send_msg(ws, \"chat:send_msg\", %{\"tokens\" => @text_token})\n\n          WsClient.assert_frame(\n            \"chat:send\",\n            %{\n              \"tokens\" => @text_token,\n              \"sentAt\" => _,\n              \"from\" => ^id,\n              \"id\" => msg_uuid,\n              \"isWhisper\" => false\n            },\n            t.client_ws\n          )\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/chat/unban_test.exs",
    "content": "defmodule BrothTest.Chat.UnbanTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket chat:ban operation\" do\n    test \"bans the person from the room chat\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      banned = %{id: banned_id} = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      # join the speaker user into the room\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg(t.client_ws, \"chat:ban\", %{\"userId\" => banned_id})\n      WsClient.assert_frame_legacy(\"chat_user_banned\", %{\"userId\" => ^banned_id}, t.client_ws)\n\n      assert Onion.Chat.banned?(room_id, banned_id)\n\n      WsClient.send_msg(t.client_ws, \"chat:unban\", %{\"userId\" => banned_id})\n      WsClient.assert_frame_legacy(\"chat_user_unbanned\", %{\"userId\" => ^banned_id}, t.client_ws)\n\n      refute Onion.Chat.banned?(room_id, banned_id)\n    end\n\n    @tag :skip\n    test \"a non-mod can't ban someone from room chat\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/message_test.exs",
    "content": "defmodule BrothTest.MessageTest do\n  @moduledoc \"generic tests on the broth message systems\"\n\n  use ExUnit.Case, async: true\n\n  alias Broth.Message\n\n  defmodule TestOperator do\n    use Broth.Message.Cast\n\n    @primary_key false\n    embedded_schema do\n      # required, may not be 42.\n      field(:foo, :integer)\n    end\n\n    import Ecto.Changeset\n\n    def changeset(changeset \\\\ %__MODULE__{}, data) do\n      changeset\n      |> cast(data, [:foo])\n      |> validate_number(:foo, not_equal_to: 42, message: \"bad number\")\n      |> validate_required(:foo)\n    end\n\n    def execute(_, state), do: {:reply, %__MODULE__{}, state}\n  end\n\n  @passing_contract %{\n    \"op\" => \"test:operator\",\n    \"p\" => %{\"foo\" => 47},\n    \"v\" => \"0.2.0\"\n  }\n\n  defp validate(data) do\n    data\n    |> Broth.Message.changeset(%{})\n    |> Ecto.Changeset.apply_action(:validate)\n  end\n\n  describe \"for a generic contract\" do\n    test \"the contract system allows conversion\" do\n      assert {:ok,\n              %Message{\n                operator: TestOperator,\n                payload: inner_changeset = %{valid?: true}\n              }} = validate(@passing_contract)\n\n      assert {:ok, %TestOperator{}} = Ecto.Changeset.apply_action(inner_changeset, :validate)\n    end\n\n    @bad_data put_in(@passing_contract, [\"p\", \"foo\"], 42)\n\n    test \"the contract system collapses inner errors into the message.\" do\n      assert {:ok, %{errors: %{foo: \"bad number\"}}} = validate(@bad_data)\n    end\n\n    @missing_data put_in(@passing_contract, [\"p\"], %{})\n\n    test \"the contract system fails when payload data are omitted\" do\n      assert {:ok, %{errors: %{foo: \"can't be blank\"}}} = validate(@missing_data)\n    end\n\n    @invalid_data put_in(@passing_contract, [\"p\", \"foo\"], \"bar\")\n    test \"invalid datatypes are not accepted\" do\n      assert {:ok, %{errors: %{foo: \"is invalid\"}}} = validate(@invalid_data)\n    end\n  end\n\n  describe \"an invalid operator\" do\n    @operatorless Map.delete(@passing_contract, \"op\")\n\n    test \"because it's missing fails\" do\n      assert {:error, %{errors: [operator: {\"no operator present\", _}]}} = validate(@operatorless)\n    end\n\n    @invalid_operator Map.put(@passing_contract, \"op\", \"foobarbaz\")\n    test \"because it's not implemented fails\" do\n      assert {:error, %{errors: [operator: {\"foobarbaz is invalid\", _}]}} =\n               validate(@invalid_operator)\n    end\n  end\n\n  describe \"an invalid version\" do\n    @versionless Map.delete(@passing_contract, \"v\")\n\n    test \"because it's missing fails\" do\n      assert {:error, %{errors: [version: {\"no version present\", _}]}} = validate(@versionless)\n    end\n\n    @invalid_version Map.put(@passing_contract, \"v\", \"foobarbaz\")\n    test \"because it's not implemented fails\" do\n      assert {:error, %{errors: [version: {\"is invalid\", _}]}} = validate(@invalid_version)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/misc/search_test.exs",
    "content": "defmodule BrothTest.Misc.Search do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket misc:search operation\" do\n    test \"returns public room if query matches\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"misc:search\",\n          %{query: \"foo\"}\n        )\n\n      WsClient.assert_reply(\n        \"misc:search:reply\",\n        ref,\n        %{\"items\" => [%{\"id\" => ^room_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"doesn't return a room if it's private\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"misc:search\",\n          %{query: \"foo\"}\n        )\n\n      WsClient.assert_reply(\n        \"misc:search:reply\",\n        ref,\n        %{\"items\" => []},\n        t.client_ws\n      )\n    end\n\n    test \"returns user if query matches\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"misc:search\",\n          %{query: \"@\" <> t.user.username}\n        )\n\n      u_id = t.user.id\n\n      WsClient.assert_reply(\n        \"misc:search:reply\",\n        ref,\n        %{\"items\" => [%{\"id\" => ^u_id}]},\n        t.client_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/ban_test.exs",
    "content": "defmodule BrothTest.Room.BanTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:ban operation\" do\n    test \"blocks that person from a room\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      blocked = %{id: blocked_id} = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      # join the blocked user into the room\n      WsClient.do_call(blocked_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # block the person.\n      WsClient.send_msg(t.client_ws, \"room:ban\", %{\"userId\" => blocked_id})\n\n      WsClient.assert_frame_legacy(\n        \"user_left_room\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^blocked_id},\n        t.client_ws\n      )\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n    end\n\n    test \"blocks person's ip from a room\", t do\n      # first, create a room owned by the test user.\n      {:ok, %{room: %{id: room_id}}} = Kousa.Room.create_room(t.user.id, \"foo room\", \"foo\", false)\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      blocked = %{id: blocked_id} = Factory.create(User)\n      WsClientFactory.create_client_for(blocked)\n      # make sure ip got saved\n      %User{ip: str_ip} = Users.get_by_id(blocked.id)\n      refute is_nil(str_ip)\n\n      # join the blocked user into the room\n      Kousa.Room.join_room(blocked_id, room_id)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      # block the person.\n      WsClient.send_msg(t.client_ws, \"room:ban\", %{\"userId\" => blocked_id, \"shouldBanIp\" => true})\n\n      WsClient.assert_frame_legacy(\n        \"user_left_room\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^blocked_id},\n        t.client_ws\n      )\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n      also_blocked = Factory.create(User)\n      WsClientFactory.create_client_for(also_blocked)\n      assert Beef.RoomBlocks.blocked?(room_id, also_blocked.id)\n    end\n\n    test \"block then block person's ip from a room\", t do\n      # first, create a room owned by the test user.\n      {:ok, %{room: %{id: room_id}}} = Kousa.Room.create_room(t.user.id, \"foo room\", \"foo\", false)\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      blocked = %{id: blocked_id} = Factory.create(User)\n      WsClientFactory.create_client_for(blocked)\n      # make sure ip got saved\n      %User{ip: str_ip} = Users.get_by_id(blocked.id)\n      refute is_nil(str_ip)\n\n      # join the blocked user into the room\n      Kousa.Room.join_room(blocked_id, room_id)\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      WsClient.send_msg(t.client_ws, \"room:ban\", %{\"userId\" => blocked_id})\n      WsClient.send_msg(t.client_ws, \"room:ban\", %{\"userId\" => blocked_id, \"shouldBanIp\" => true})\n\n      WsClient.assert_frame_legacy(\n        \"user_left_room\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^blocked_id},\n        t.client_ws\n      )\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n      also_blocked = Factory.create(User)\n      WsClientFactory.create_client_for(also_blocked)\n      assert Beef.RoomBlocks.blocked?(room_id, also_blocked.id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/create_scheduled_test.exs",
    "content": "defmodule BrothTest.Room.CreateScheduledTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:create_scheduled operation\" do\n    test \"edits a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:create_scheduled\",\n          %{\"name\" => \"foo room\", \"scheduledFor\" => DateTime.to_iso8601(time)}\n        )\n\n      WsClient.assert_reply(\n        \"room:create_scheduled:reply\",\n        ref,\n        %{\"id\" => room_id, \"name\" => \"foo room\"}\n      )\n\n      assert %{name: \"foo room\"} = Beef.ScheduledRooms.get_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/create_test.exs",
    "content": "defmodule BrothTest.Room.CreateTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Schemas.ScheduledRoom\n  alias Beef.Users\n  alias Beef.ScheduledRooms\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:create operation\" do\n    test \"joins the user to the room\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:create\",\n          %{\n            \"name\" => \"foo room\",\n            \"description\" => \"baz quux\",\n            \"isPrivate\" => true\n          }\n        )\n\n      WsClient.assert_reply(\n        \"room:create:reply\",\n        ref,\n        %{\n          \"creatorId\" => ^user_id,\n          \"description\" => \"baz quux\",\n          \"id\" => room_id,\n          \"name\" => \"foo room\",\n          \"isPrivate\" => true\n        }\n      )\n\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n    end\n\n    test \"can pass null description\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:create\",\n          %{\n            \"name\" => \"foo room\",\n            \"description\" => nil,\n            \"isPrivate\" => true\n          }\n        )\n\n      WsClient.assert_reply(\n        \"room:create:reply\",\n        ref,\n        %{\n          \"creatorId\" => ^user_id,\n          \"description\" => \"\",\n          \"id\" => room_id,\n          \"name\" => \"foo room\",\n          \"isPrivate\" => true\n        }\n      )\n\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n    end\n\n    test \"can pass scheduled room id\", t do\n      user_id = t.user.id\n      scheduled_room = Factory.create(ScheduledRoom, creatorId: user_id)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:create\",\n          %{\n            \"name\" => \"foo room\",\n            \"description\" => nil,\n            \"scheduledRoomId\" => scheduled_room.id\n          }\n        )\n\n      WsClient.assert_reply(\n        \"room:create:reply\",\n        ref,\n        %{\n          \"creatorId\" => ^user_id,\n          \"description\" => \"\",\n          \"id\" => room_id,\n          \"name\" => \"foo room\",\n          \"isPrivate\" => false\n        }\n      )\n\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      assert %ScheduledRoom{started: true, roomId: ^room_id} =\n               ScheduledRooms.get_by_id(scheduled_room.id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/deafen_test.exs",
    "content": "defmodule BrothTest.Room.DeafenTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket room:deafen operation\" do\n    test \"can be used to deafen\", t do\n      room_id = t.room_id\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # deaf ON\n      ref = WsClient.send_call(t.client_ws, \"room:deafen\", %{\"deafened\" => true})\n      WsClient.assert_reply(\"room:deafen:reply\", ref, _)\n      Process.sleep(100)\n      map = Onion.RoomSession.get(room_id, :deafMap)\n      assert is_map_key(map, t.user.id)\n\n      # deaf OFF\n      ref = WsClient.send_call(t.client_ws, \"room:deafen\", %{\"deafened\" => false})\n      WsClient.assert_reply(\"room:deafen:reply\", ref, _)\n      Process.sleep(100)\n      map = Onion.RoomSession.get(room_id, :deafMap)\n      refute is_map_key(map, t.user.id)\n    end\n\n    test \"can be used to undeafen\", t do\n      room_id = t.room_id\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      ref = WsClient.send_call(t.client_ws, \"room:deafen\", %{\"deafened\" => false})\n\n      WsClient.assert_reply(\"room:deafen:reply\", ref, _)\n\n      map = Onion.RoomSession.get(room_id, :deafMap)\n\n      assert map == %{}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/delete_scheduled_test.exs",
    "content": "defmodule BrothTest.Room.DeleteScheduledTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"when you supply roomId to room:delete_scheduled \" do\n    test \"it deletes the room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      {:ok, %{id: room_id}} =\n        Kousa.ScheduledRoom.schedule(user_id, %{\n          \"name\" => \"foo room\",\n          \"scheduledFor\" => time\n        })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:delete_scheduled\",\n          %{\"roomId\" => room_id}\n        )\n\n      WsClient.assert_reply(\n        \"room:delete_scheduled:reply\",\n        ref,\n        %{}\n      )\n\n      refute Beef.ScheduledRooms.get_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/get_banned_users_test.exs",
    "content": "defmodule BrothTest.Room.GetBannedUsersTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:get_banned_users operation\" do\n    test \"returns one banned user if you are in the room\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      # make user to ban and put them in the room\n      user_to_ban = Factory.create(User)\n      user_to_ban_ws = WsClientFactory.create_client_for(user_to_ban)\n      WsClient.do_call(user_to_ban_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_to_ban.id)\n      Kousa.Room.block_from_room(user_id, user_to_ban.id)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_banned_users\",\n          %{}\n        )\n\n      banned_user_id = user_to_ban.id\n\n      WsClient.assert_reply(\n        \"room:get_banned_users:reply\",\n        ref,\n        %{\n          \"users\" => [%{\"id\" => ^banned_user_id}]\n        },\n        t.client_ws\n      )\n    end\n\n    test \"returns what if you're not in a room\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_banned_users\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_banned_users:reply\",\n        ref,\n        %{\"users\" => []},\n        t.client_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/get_info_test.exs",
    "content": "defmodule BrothTest.Room.GetInfoTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket room:get_info operation\" do\n    test \"can get your own user info\", t do\n      room_id = t.room_id\n\n      ref = WsClient.send_call(t.client_ws, \"room:get_info\", %{\"id\" => room_id})\n\n      WsClient.assert_reply(\n        \"room:get_info:reply\",\n        ref,\n        %{\"id\" => ^room_id, \"name\" => \"foo room\"}\n      )\n    end\n\n    test \"if you don't supply id, then you'll get the room you're in\", t do\n      room_id = t.room_id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_info\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_info:reply\",\n        ref,\n        %{\"id\" => ^room_id, \"name\" => \"foo room\"}\n      )\n    end\n\n    @tag :skip\n    test \"what happens if you aren't in a room and supply room id\"\n\n    @tag :skip\n    test \"what happens when you try to do room id of a private room\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/get_invite_list_test.exs",
    "content": "defmodule BrothTest.Room.GetInviteListTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:get_invite_list operation\" do\n    test \"gets an empty list when you haven't invited anyone\", t do\n      ref = WsClient.send_call(t.client_ws, \"room:get_invite_list\", %{\"cursor\" => 0})\n      WsClient.assert_reply(\"room:get_invite_list:reply\", ref, %{\"invites\" => []}, t.client_ws)\n    end\n\n    test \"returns one user when you have invited them\", t do\n      # create a follower user that is logged in.\n      follower = %{id: follower_id} = Factory.create(User)\n      WsClientFactory.create_client_for(follower)\n      Kousa.Follow.follow(follower_id, t.user.id, true)\n\n      WsClient.send_msg(t.client_ws, \"room:invite\", %{\"userId\" => follower_id})\n\n      ref = WsClient.send_call(t.client_ws, \"room:get_invite_list\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply(\n        \"room:get_invite_list:reply\",\n        ref,\n        %{\"invites\" => [%{\"id\" => ^follower_id}]},\n        t.client_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/get_scheduled_test.exs",
    "content": "defmodule BrothTest.Room.GetScheduledTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"when you supply your userId to room:get_scheduled \" do\n    test \"it returns no rooms if there are none\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{\"range\" => \"upcoming\", \"userId\" => t.user.id}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\"rooms\" => []}\n      )\n    end\n\n    test \"it returns most recent rooms\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      Kousa.ScheduledRoom.schedule(user_id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{\"range\" => \"upcoming\", \"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\n          \"rooms\" => [\n            %{\n              \"creator\" => %{\"id\" => ^user_id},\n              \"name\" => \"foo room\",\n              \"scheduledFor\" => the_future\n            }\n          ]\n        }\n      )\n\n      assert DateTime.to_iso8601(time) == the_future\n    end\n\n    test \"it doesn't return other user's rooms\", t do\n      other_user = Factory.create(User)\n\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      Kousa.ScheduledRoom.schedule(other_user.id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{\"range\" => \"upcoming\", \"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\n          \"rooms\" => []\n        }\n      )\n    end\n\n    test \"it won't return a far future room\", t do\n      time = DateTime.utc_now() |> DateTime.add(3600, :second)\n      user_id = t.user.id\n\n      Kousa.ScheduledRoom.schedule(user_id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{\"range\" => \"upcoming\", \"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\n          \"rooms\" => []\n        }\n      )\n    end\n\n    test \"it will return a far future room if range is set to all\", t do\n      time = DateTime.utc_now() |> DateTime.add(3600, :second)\n      user_id = t.user.id\n\n      Kousa.ScheduledRoom.schedule(user_id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{\"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\n          \"rooms\" => [%{\"scheduledFor\" => the_future}]\n        }\n      )\n\n      assert DateTime.to_iso8601(time) == the_future\n    end\n\n    @tag :skip\n    test \"you can supply someone else's userId\"\n  end\n\n  describe \"when you don't supply your userId to room:get_scheduled \" do\n    test \"it returns no rooms if there are none\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\"rooms\" => []}\n      )\n    end\n\n    test \"it returns other rooms\", t do\n      other_user = Factory.create(User)\n\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = other_user.id\n\n      Kousa.ScheduledRoom.schedule(user_id, %{\n        \"name\" => \"foo room\",\n        \"scheduledFor\" => time\n      })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_scheduled\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_scheduled:reply\",\n        ref,\n        %{\n          \"rooms\" => [\n            %{\n              \"creator\" => %{\"id\" => ^user_id},\n              \"name\" => \"foo room\",\n              \"scheduledFor\" => the_future\n            }\n          ]\n        }\n      )\n\n      assert DateTime.to_iso8601(time) == the_future\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/get_top_test.exs",
    "content": "defmodule BrothTest.Room.GetTopTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:get_top operation\" do\n    test \"returns one public room if it's the only one\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_top\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_top:reply\",\n        ref,\n        %{\"rooms\" => [%{\"id\" => ^room_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"doesn't return a room if it's private\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_top\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_top:reply\",\n        ref,\n        %{\"rooms\" => []},\n        t.client_ws\n      )\n    end\n\n    test \"returns a room even if a random has blocked me\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      random_who_blocked_me = Factory.create(User)\n\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => random_who_blocked_me.id})\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_top\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_top:reply\",\n        ref,\n        %{\"rooms\" => [%{\"id\" => ^room_id}]},\n        t.client_ws\n      )\n    end\n\n    test \"doesn't return a room if creator user blocked me\", t do\n      user_id = t.user.id\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => false}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(user_id)\n\n      blocked = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => blocked.id})\n\n      ref =\n        WsClient.send_call(\n          blocked_ws,\n          \"room:get_top\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_top:reply\",\n        ref,\n        %{\"rooms\" => []},\n        blocked_ws\n      )\n    end\n\n    test \"doesn't return a room if I user blocked the creator\", t do\n      room_creator_i_blocked = Factory.create(User)\n      room_creator_i_blocked_ws = WsClientFactory.create_client_for(room_creator_i_blocked)\n\n      WsClient.do_call(\n        room_creator_i_blocked_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => false}\n      )\n\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => room_creator_i_blocked.id})\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:get_top\",\n          %{}\n        )\n\n      WsClient.assert_reply(\n        \"room:get_top:reply\",\n        ref,\n        %{\"rooms\" => []},\n        t.client_ws\n      )\n    end\n\n    @tag :skip\n    test \"when there's more than one room\"\n\n    @tag :skip\n    test \"cursors also work\"\n\n    @tag :skip\n    test \"there is a maximum limit to the cursor\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/invite_test.exs",
    "content": "defmodule BrothTest.Room.InviteTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:invite operation\" do\n    test \"invites that person to a room\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a follower user that is logged in.\n      follower = %{id: follower_id} = Factory.create(User)\n      follower_ws = WsClientFactory.create_client_for(follower)\n      Kousa.Follow.follow(follower_id, t.user.id, true)\n\n      WsClient.send_msg(t.client_ws, \"room:invite\", %{\"userId\" => follower_id})\n\n      # note this comes from the follower's client\n      WsClient.assert_frame_legacy(\"invitation_to_room\", %{\"roomId\" => ^room_id}, follower_ws)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/join_test.exs",
    "content": "defmodule BrothTest.Room.JoinTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.UserBlocks\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:join operation\" do\n    test \"joins a user to a room\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n\n      ref =\n        WsClient.send_call(\n          other_ws,\n          \"room:join\",\n          %{\"roomId\" => room_id}\n        )\n\n      WsClient.assert_reply(\n        \"room:join:reply\",\n        ref,\n        %{\n          \"description\" => \"foo\",\n          \"id\" => room_id,\n          \"name\" => \"foo room\",\n          \"isPrivate\" => false\n        }\n      )\n\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(other.id)\n    end\n  end\n\n  describe \"the user cannot join a room\" do\n    test \"if the creator blocked them\", t do\n      %{\"id\" => room_id, \"creatorId\" => creatorId} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # create a user that is logged in.\n      blocked = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      # block the new user\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => blocked.id})\n\n      assert UserBlocks.blocked?(creatorId, blocked.id)\n\n      ref =\n        WsClient.send_call(\n          blocked_ws,\n          \"room:join\",\n          %{\"roomId\" => room_id}\n        )\n\n      WsClient.assert_error(\n        \"room:join\",\n        ref,\n        %{\n          \"message\" => \"the creator of the room blocked you\"\n        }\n      )\n\n      assert %{currentRoomId: nil} = Users.get_by_id(blocked.id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/leave_test.exs",
    "content": "defmodule BrothTest.Room.LeaveTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias Beef.Rooms\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket room:leave operation\" do\n    test \"deletes the room if they are the only person\", t do\n      room_id = t.room_id\n\n      assert Users.get_by_id(t.user.id).currentRoomId == room_id\n\n      ref = WsClient.send_call(t.client_ws, \"room:leave\", %{})\n\n      WsClient.assert_reply(\"room:leave:reply\", ref, _)\n\n      refute Users.get_by_id(t.user.id).currentRoomId\n      refute Rooms.get_room_by_id(room_id)\n    end\n\n    test \"removes the person from the room if they aren't the only person\", t do\n      user_id = t.user.id\n      room_id = t.room_id\n\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n\n      assert %{peoplePreviewList: [_]} = Rooms.get_room_by_id(room_id)\n\n      WsClient.do_call(other_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      assert %{peoplePreviewList: [_, _]} = Rooms.get_room_by_id(room_id)\n\n      ref = WsClient.send_call(other_ws, \"room:leave\", %{})\n\n      WsClient.assert_reply(\"room:leave:reply\", ref, _)\n\n      assert %{\n               peoplePreviewList: [\n                 %{id: ^user_id}\n               ]\n             } = Rooms.get_room_by_id(room_id)\n    end\n\n    @tag :skip\n    test \"informs multiple clients that the room has been left\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/mute_test.exs",
    "content": "defmodule BrothTest.Room.MuteTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket room:mute operation\" do\n    test \"can be used to mute\", t do\n      # first, create a room owned by the primary user.\n      room_id = t.room_id\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # mute ON\n      ref = WsClient.send_call(t.client_ws, \"room:mute\", %{\"muted\" => true})\n      WsClient.assert_reply(\"room:mute:reply\", ref, _)\n      Process.sleep(100)\n      map = Onion.RoomSession.get(room_id, :muteMap)\n      assert is_map_key(map, t.user.id)\n\n      # mute OFF\n      ref = WsClient.send_call(t.client_ws, \"room:mute\", %{\"muted\" => false})\n      WsClient.assert_reply(\"room:mute:reply\", ref, _)\n      Process.sleep(100)\n      map = Onion.RoomSession.get(room_id, :muteMap)\n      refute is_map_key(map, t.user.id)\n    end\n\n    test \"can be used to unmute\", t do\n      # first, create a room owned by the primary user.\n      room_id = t.room_id\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      ref = WsClient.send_call(t.client_ws, \"room:mute\", %{\"muted\" => false})\n\n      WsClient.assert_reply(\"room:mute:reply\", ref, _)\n\n      map = Onion.RoomSession.get(room_id, :muteMap)\n\n      assert map == %{}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/set_active_speaker_test.exs",
    "content": "defmodule BrothTest.Room.SetActiveSpeakerTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the websocket room:set_active_speaker operation\" do\n    test \"toggles the active speaking state\", t do\n      room_id = t.room_id\n\n      # add a second user to the test\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n      WsClient.do_call(other_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      assert %{} = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      WsClient.send_msg(\n        t.client_ws,\n        \"room:set_active_speaker\",\n        %{\"active\" => true}\n      )\n\n      # both websockets will be informed\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        t.client_ws\n      )\n\n      assert is_map_key(map, t.user.id)\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        other_ws\n      )\n\n      assert is_map_key(map, t.user.id)\n\n      map = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      assert is_map_key(map, t.user.id)\n\n      Process.sleep(100)\n\n      WsClient.send_msg(\n        t.client_ws,\n        \"room:set_active_speaker\",\n        %{\"active\" => false}\n      )\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        t.client_ws\n      )\n\n      refute is_map_key(map, t.user.id)\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map},\n        other_ws\n      )\n\n      refute is_map_key(map, t.user.id)\n\n      map = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      refute is_map_key(map, t.user.id)\n    end\n\n    test \"does nothing if it's unset\", t do\n      room_id = t.room_id\n\n      # add a second user to the test\n      other = Factory.create(User)\n      other_ws = WsClientFactory.create_client_for(other)\n      WsClient.do_call(other_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      WsClient.send_msg(\n        t.client_ws,\n        \"room:set_active_speaker\",\n        %{\"active\" => false}\n      )\n\n      WsClient.assert_frame_legacy(\n        \"active_speaker_change\",\n        %{\"activeSpeakerMap\" => map}\n      )\n\n      assert map == %{}\n\n      map = Onion.RoomSession.get(room_id, :activeSpeakerMap)\n\n      assert map == %{}\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/set_auth_test.exs",
    "content": "defmodule BrothTest.Room.SetAuthTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"the using room:set_auth with mod\" do\n    test \"makes the person a mod\", t do\n      room_id = t.room_id\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # make the person a mod\n      WsClient.send_msg(\n        t.client_ws,\n        \"room:set_auth\",\n        %{\n          \"userId\" => speaker_id,\n          \"level\" => \"mod\"\n        }\n      )\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"mod_changed\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"mod_changed\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      assert Beef.RoomPermissions.get(speaker_id, room_id).isMod\n    end\n  end\n\n  describe \"the set_auth command can\" do\n    test \"makes the person a room_creator\", t do\n      room_id = t.room_id\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # make the person a room creator.\n      WsClient.send_msg(t.client_ws, \"room:set_auth\", %{\n        \"userId\" => speaker_id,\n        \"level\" => \"owner\"\n      })\n\n      # NB: we get an extraneous speaker_added message here.\n      WsClient.assert_frame_legacy(\n        \"new_room_creator\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id}\n      )\n\n      assert Beef.Rooms.get_room_by_id(room_id).creatorId == speaker_id\n      assert Process.alive?(t.client_ws)\n    end\n\n    @tag :skip\n    test \"a non-owner can't make someone a room creator\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/set_role_test.exs",
    "content": "defmodule BrothTest.Room.SetRoleTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    %{\"id\" => room_id} =\n      WsClient.do_call(\n        client_ws,\n        \"room:create\",\n        %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n      )\n\n    {:ok, user: user, client_ws: client_ws, room_id: room_id}\n  end\n\n  describe \"for when you room:set_role to listener\" do\n    test \"takes a speaker and turns them into lister\", t do\n      room_id = t.room_id\n\n      # create a speaker user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n\n      Beef.RoomPermissions.set_speaker(t.user.id, room_id, true)\n\n      assert Beef.RoomPermissions.speaker?(t.user.id, room_id)\n\n      WsClient.send_msg(t.client_ws, \"room:set_role\", %{\n        \"userId\" => speaker_id,\n        \"role\" => \"listener\"\n      })\n\n      WsClient.assert_frame_legacy(\n        \"speaker_removed\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^speaker_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_removed\",\n        %{\"roomId\" => ^room_id, \"userId\" => ^speaker_id},\n        speaker_ws\n      )\n\n      refute Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n\n    test \"mods can't move other mods to listeners\", t do\n      room_id = t.room_id\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n  \n      # create a user that is logged in and will be moded.\n      mod1 = %{id: mod1_id} = Factory.create(User)\n      mod1_ws = WsClientFactory.create_client_for(mod1)\n  \n      # create another user that is logged in and will be moded.\n      mod2 = %{id: mod2_id} = Factory.create(User)\n      mod2_ws = WsClientFactory.create_client_for(mod2)\n  \n      # join the mod1 user into the room\n      WsClient.do_call(mod1_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n  \n      # join the mod2 user into the room\n      WsClient.do_call(mod2_ws, \"room:join\", %{\"roomId\" => room_id})\n      WsClient.assert_frame_legacy(\"new_user_join_room\", _)\n  \n      # make mod1 user a mod\n      Beef.RoomPermissions.set_is_mod(mod1_id, room_id, true)\n      assert Beef.RoomPermissions.mod?(mod1_id, room_id)\n  \n      # make mod2 user a mod\n      Beef.RoomPermissions.set_is_mod(mod2_id, room_id, true)\n      assert Beef.RoomPermissions.mod?(mod2_id, room_id)\n  \n      # set mod2 as speaker\n      Beef.RoomPermissions.set_speaker(mod2_id, room_id, true)\n      assert Beef.RoomPermissions.speaker?(mod2_id, room_id)\n  \n      # set mod2 as listener using mod1\n      WsClient.send_msg(mod1_ws, \"room:set_role\", %{\n        \"userId\" => mod2_id,\n        \"role\" => \"listener\"\n      })\n  \n      #mod1 can't move mod2 to listeners\n      refute Beef.RoomPermissions.listener?(mod2_id, room_id)\n    end\n\n    @tag :skip\n    test \"you can make yourself a listener\"\n\n    @tag :skip\n    test \"you can't make someone a listener unless you're a mod\"\n  end\n\n  describe \"when you set_role to speaker\" do\n    test \"makes the person a speaker\", t do\n      room_id = t.room_id\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      refute Beef.RoomPermissions.speaker?(speaker_id, room_id)\n      Kousa.Room.set_role(speaker_id, :raised_hand, by: t.user.id)\n      assert Beef.RoomPermissions.asked_to_speak?(speaker_id, room_id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # add the person as a speaker.\n      WsClient.send_msg(\n        t.client_ws,\n        \"room:set_role\",\n        %{\"userId\" => speaker_id, \"role\" => \"speaker\"}\n      )\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      assert Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n\n    test \"ask to speak makes you a speaker when auto speaker is on\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"autoSpeaker\" => true}\n        )\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      WsClient.do_call(speaker_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      refute Beef.RoomPermissions.speaker?(speaker_id, room_id)\n      Kousa.Room.set_role(speaker_id, :raised_hand, by: t.user.id)\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        t.client_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      assert Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n\n    test \"can only make them a speaker if they asked to speak\", t do\n      # first, create a room owned by the primary user.\n      {:ok, %{room: %{id: room_id}}} = Kousa.Room.create_room(t.user.id, \"foo room\", \"foo\", false)\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n\n      refute Beef.RoomPermissions.speaker?(speaker.id, room_id)\n\n      # join the speaker user into the room\n      Kousa.Room.join_room(speaker_id, room_id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # add the person as a speaker.\n      WsClient.send_msg(\n        t.client_ws,\n        \"room:set_role\",\n        %{\"userId\" => speaker_id, \"role\" => \"speaker\"}\n      )\n\n      refute Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n\n    test \"mod can make the person a speaker\", t do\n      room_id = t.room_id\n\n      # create a user that is logged in.\n      speaker = %{id: speaker_id} = Factory.create(User)\n      speaker_ws = WsClientFactory.create_client_for(speaker)\n\n      # join the speaker user into the room\n      Kousa.Room.join_room(speaker_id, room_id)\n      Kousa.Room.set_role(speaker_id, :raised_hand, by: t.user.id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^speaker_id}})\n\n      # create mod\n      mod = %{id: mod_id} = Factory.create(User)\n      mod_ws = WsClientFactory.create_client_for(mod)\n\n      Kousa.Room.join_room(mod_id, room_id)\n\n      WsClient.assert_frame_legacy(\"new_user_join_room\", %{\"user\" => %{\"id\" => ^mod_id}})\n\n      Kousa.Room.set_auth(mod_id, :mod, by: t.user.id)\n\n      # add the person as a speaker.\n      WsClient.send_msg(\n        mod_ws,\n        \"room:set_role\",\n        %{\"userId\" => speaker_id, \"role\" => \"speaker\"}\n      )\n\n      # both clients get notified\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        mod_ws\n      )\n\n      WsClient.assert_frame_legacy(\n        \"speaker_added\",\n        %{\"userId\" => ^speaker_id, \"roomId\" => ^room_id},\n        speaker_ws\n      )\n\n      assert Beef.RoomPermissions.speaker?(speaker_id, room_id)\n    end\n\n    @tag :skip\n    test \"you can't make a person a speaker if you aren't a mod\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/unban_test.exs",
    "content": "defmodule BrothTest.Room.UnbanTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:unban operation\" do\n    test \"blocks that person from a room\", t do\n      # first, create a room owned by the test user.\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n\n      # create a blocked user that is logged in.\n      %{id: blocked_id} = Factory.create(User)\n\n      Beef.RoomBlocks.insert(%{\n        userId: blocked_id,\n        roomId: room_id,\n        modId: t.user.id\n      })\n\n      assert Beef.RoomBlocks.blocked?(room_id, blocked_id)\n\n      # block the person.\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:unban\",\n          %{\"userId\" => blocked_id}\n        )\n\n      WsClient.assert_reply(\"room:unban:reply\", ref, %{}, t.client_ws)\n\n      refute Beef.RoomBlocks.blocked?(room_id, blocked_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/update_scheduled_test.exs",
    "content": "defmodule BrothTest.Room.UpdateScheduledTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:update_scheduled operation\" do\n    test \"edits a scheduled room\", t do\n      time = DateTime.utc_now() |> DateTime.add(10, :second)\n      user_id = t.user.id\n\n      {:ok, %{id: room_id}} =\n        Kousa.ScheduledRoom.schedule(user_id, %{\n          \"name\" => \"foo room\",\n          \"scheduledFor\" => time\n        })\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:update_scheduled\",\n          %{\"id\" => room_id, \"name\" => \"bar room\"}\n        )\n\n      WsClient.assert_reply(\n        \"room:update_scheduled:reply\",\n        ref,\n        %{\"name\" => \"bar room\"}\n      )\n\n      assert %{name: \"bar room\"} = Beef.ScheduledRooms.get_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/room/update_test.exs",
    "content": "defmodule BrothTest.Room.UpdateTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias Beef.Rooms\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket room:update operation\" do\n    test \"makes the room public\", t do\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          t.client_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\", \"isPrivate\" => true}\n        )\n\n      # make sure the user is in there.\n      assert %{currentRoomId: ^room_id} = Users.get_by_id(t.user.id)\n      # make sure the room is private\n\n      assert Rooms.get_room_by_id(room_id).isPrivate\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"room:update\",\n          %{\"name\" => \"quux room\", \"isPrivate\" => false}\n        )\n\n      WsClient.assert_reply(\n        \"room:update:reply\",\n        ref,\n        %{\"name\" => \"quux room\", \"isPrivate\" => false}\n      )\n\n      # make sure the room is actually private\n      assert %{\n               isPrivate: false,\n               name: \"quux room\"\n             } = Rooms.get_room_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/admin_update_test.exs",
    "content": "defmodule BrothTest.User.AdminUpdateTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  @ben_github_id Application.compile_env!(:kousa, :ben_github_id)\n\n  setup do\n    user = Factory.create(User, githubId: @ben_github_id)\n    staff_user = Factory.create(User)\n\n    client_ws = WsClientFactory.create_client_for(user)\n    staff_client_ws = WsClientFactory.create_client_for(staff_user)\n\n    {:ok,\n     user: user, staff_user: staff_user, staff_client_ws: staff_client_ws, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:admin_update operation\" do\n    test \"doesn't work for not-ben awad\", t do\n      ref =\n        WsClient.send_call(t.staff_client_ws, \"user:admin_update\", %{\n          \"id\" => t.staff_user.id,\n          \"user\" => %{\n            \"staff\" => true,\n            \"contributions\" => 100\n          }\n        })\n\n      WsClient.assert_error(\"user:admin_update\", ref, %{\"message\" => \"not authorized\"})\n    end\n\n    test \"works for ben awad\", t do\n      ref =\n        WsClient.send_call(t.client_ws, \"user:admin_update\", %{\n          \"id\" => t.staff_user.id,\n          \"user\" => %{\n            \"staff\" => true,\n            \"contributions\" => 100\n          }\n        })\n\n      WsClient.assert_reply(\"user:admin_update:reply\", ref, %{\n        \"staff\" => true,\n        \"contributions\" => 100\n      })\n\n      # check that the user has been updated.\n      assert %{staff: true, contributions: 100} = Users.get_by_id(t.staff_user.id)\n    end\n\n    test \"staff can update staff\", t do\n      staff = Factory.create(User, staff: true)\n      staff_ws = WsClientFactory.create_client_for(staff)\n      should_become_staff = Factory.create(User)\n\n      ref =\n        WsClient.send_call(staff_ws, \"user:admin_update\", %{\n          \"id\" => should_become_staff.id,\n          \"user\" => %{\n            \"staff\" => true,\n            \"contributions\" => 100\n          }\n        })\n\n      WsClient.assert_reply(\"user:admin_update:reply\", ref, %{\n        \"staff\" => true,\n        \"contributions\" => 100\n      })\n\n      # check that the user has been updated.\n      assert %{staff: true, contributions: 100} = Users.get_by_id(should_become_staff.id)\n    end\n\n    test \"can update single field\", t do\n      WsClient.do_call(t.client_ws, \"user:admin_update\", %{\n        \"id\" => t.staff_user.id,\n        \"user\" => %{\n          \"staff\" => true,\n          \"contributions\" => 100\n        }\n      })\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:admin_update\", %{\n          \"id\" => t.staff_user.id,\n          \"user\" => %{\n            \"staff\" => false\n          }\n        })\n\n      WsClient.assert_reply(\"user:admin_update:reply\", ref, %{\n        \"staff\" => false,\n        \"contributions\" => 100\n      })\n\n      # check that the user has been updated.\n      assert %{staff: false, contributions: 100} = Users.get_by_id(t.staff_user.id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/ban_test.exs",
    "content": "defmodule BrothTest.User.BanTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:ban operation\" do\n    test \"doesn't work for not-ben awad\", t do\n      banned = Factory.create(User)\n      WsClientFactory.create_client_for(banned)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:ban\", %{\n          \"userId\" => banned.id,\n          \"reason\" => \"you're a douche\"\n        })\n\n      WsClient.assert_error(\"user:ban\", ref, %{\"message\" => message})\n      assert message =~ \"but that user didn't exist\"\n    end\n\n    @ben_github_id Application.compile_env!(:kousa, :ben_github_id)\n\n    test \"works for ben awad\", t do\n      t.user\n      |> User.changeset(%{githubId: @ben_github_id})\n      |> Beef.Repo.update!()\n\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:ban\", %{\n          \"userId\" => banned.id,\n          \"reason\" => \"you're a douche\"\n        })\n\n      WsClient.assert_reply(\"user:ban:reply\", ref, reply)\n      refute is_map_key(reply, \"error\")\n\n      # this frame is targetted to the banned user\n      WsClient.assert_frame_legacy(\"banned\", _, banned_ws)\n\n      # check that the user has been updated.\n      assert %{reasonForBan: \"you're a douche\"} = Users.get_by_id(banned.id)\n    end\n\n    test \"will destroy a room if they are alone\", t do\n      t.user\n      |> User.changeset(%{githubId: @ben_github_id})\n      |> Beef.Repo.update!()\n\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          banned_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      WsClient.do_call(t.client_ws, \"user:ban\", %{\n        \"userId\" => banned.id,\n        \"reason\" => \"you're a douche\"\n      })\n\n      # note: targeted to banned_ws\n      WsClient.assert_frame_legacy(\"banned\", _, banned_ws)\n\n      # check that the room is gone.\n      refute Beef.Rooms.get_room_by_id(room_id)\n    end\n\n    test \"will eject a user from a room if they aren't alone\", t do\n      t.user\n      |> User.changeset(%{githubId: @ben_github_id})\n      |> Beef.Repo.update!()\n\n      banned = Factory.create(User)\n      banned_ws = WsClientFactory.create_client_for(banned)\n\n      safe = %{id: safe_id} = Factory.create(User)\n      safe_ws = WsClientFactory.create_client_for(safe)\n\n      %{\"id\" => room_id} =\n        WsClient.do_call(\n          safe_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      # join the banned user to the room\n      WsClient.do_call(banned_ws, \"room:join\", %{\"roomId\" => room_id})\n\n      assert %{peoplePreviewList: [_, _]} = Beef.Rooms.get_room_by_id(room_id)\n\n      WsClient.send_call(t.client_ws, \"user:ban\", %{\n        \"userId\" => banned.id,\n        \"reason\" => \"you're a douche\"\n      })\n\n      WsClient.assert_frame_legacy(\"banned\", _, banned_ws)\n\n      # check that the room is still there and the safe user is there\n      assert %{\n               peoplePreviewList: [%{id: ^safe_id}]\n             } = Beef.Rooms.get_room_by_id(room_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/block_test.exs",
    "content": "defmodule BrothTest.User.BlockTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:block operation\" do\n    test \"blocks that person from the user\", t do\n      # create a blocked user that is logged in.\n      %{id: blocked_id} = Factory.create(User)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:block\",\n          %{\"userId\" => blocked_id}\n        )\n\n      # assert that you get a response that is yourself with an updated\n      # block list.\n      WsClient.assert_reply(\n        \"user:block:reply\",\n        ref,\n        %{\"blocked\" => [^blocked_id]},\n        t.client_ws\n      )\n\n      assert Beef.UserBlocks.blocked?(t.user.id, blocked_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/create_bot_test.exs",
    "content": "defmodule BrothTest.User.CreateBotTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:create_bot operation\" do\n    test \"creates new user with username\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:create_bot\",\n          %{\n            \"username\" => \"qowidjoqwd\"\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:create_bot:reply\",\n        ref,\n        %{\"apiKey\" => apiKey}\n      )\n\n      assert {:ok, _} = Ecto.UUID.cast(apiKey)\n      %{botOwnerId: botOwnerId} = Users.get_by_api_key(apiKey)\n      user_id = t.user.id\n      assert user_id == botOwnerId\n    end\n\n    test \"returns error for username that's already taken\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:create_bot\",\n          %{\n            \"username\" => t.user.username\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:create_bot:reply\",\n        ref,\n        %{\"isUsernameTaken\" => true}\n      )\n    end\n\n    test \"bot accounts can't create bot accounts\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:create_bot\",\n          %{\n            \"username\" => \"oqieuoqw\"\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:create_bot:reply\",\n        ref,\n        %{\"apiKey\" => apiKey}\n      )\n\n      assert {:ok, _} = Ecto.UUID.cast(apiKey)\n      user = Users.get_by_api_key(apiKey)\n      bot_ws = WsClientFactory.create_client_for(user)\n\n      bot_ref =\n        WsClient.send_call(\n          bot_ws,\n          \"user:create_bot\",\n          %{\n            \"username\" => \"qowidjoqwdqwe\"\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:create_bot:reply\",\n        bot_ref,\n        %{\"error\" => \"bots can't create bots\"}\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/follow_test.exs",
    "content": "defmodule BrothTest.User.FollowTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the user:follow operation\" do\n    test \"causes you to follow\", t do\n      followed = Factory.create(User)\n\n      refute Beef.Follows.following_me?(followed.id, t.user.id)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:follow\", %{\n          \"userId\" => followed.id\n        })\n\n      WsClient.assert_reply(\"user:follow:reply\", ref, %{})\n\n      assert Beef.Follows.following_me?(followed.id, t.user.id)\n    end\n\n    @tag :skip\n    test \"you can't follow yourself?\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/get_bots_test.exs",
    "content": "defmodule BrothTest.User.GetBotsTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the user:get_bots operation\" do\n    test \"gets bots\", t do\n      WsClient.do_call(t.client_ws, \"user:create_bot\", %{\n        \"username\" => \"marvin\"\n      })\n\n      ref = WsClient.send_call(t.client_ws, \"user:get_bots\", %{})\n\n      WsClient.assert_reply(\"user:get_bots:reply\", ref, %{\"bots\" => [%{\"username\" => \"marvin\"}]})\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/get_followers_test.exs",
    "content": "defmodule BrothTest.User.GetFollowersTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:get_followers operation\" do\n    test \"returns an empty list if no one is following you\", t do\n      ref = WsClient.send_call(t.client_ws, \"user:get_followers\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply(\"user:get_followers:reply\", ref, %{\"followers\" => []})\n    end\n\n    test \"returns that person if someone is following you\", t do\n      %{id: follower_id} = Factory.create(User)\n      Kousa.Follow.follow(follower_id, t.user.id, true)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:get_followers\", %{\n          \"cursor\" => 0\n        })\n\n      WsClient.assert_reply(\"user:get_followers:reply\", ref, %{\n        \"followers\" => [\n          %{\n            \"id\" => ^follower_id\n          }\n        ]\n      })\n    end\n\n    test \"can get followers for someone else\", t do\n      %{id: following_id, username: username} = Factory.create(User)\n      Kousa.Follow.follow(t.user.id, following_id, true)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:get_followers\", %{\n          \"cursor\" => 0,\n          \"username\" => username\n        })\n\n      user_id = t.user.id\n\n      WsClient.assert_reply(\"user:get_followers:reply\", ref, %{\n        \"followers\" => [\n          %{\n            \"id\" => ^user_id\n          }\n        ]\n      })\n    end\n\n    @tag :skip\n    test \"you can't stalk someone who has blocked you\"\n\n    @tag :skip\n    test \"test proper pagination of user:get_following\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/get_following_test.exs",
    "content": "defmodule BrothTest.User.GetFollowingTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:get_following operation\" do\n    test \"returns an empty list if you aren't following anyone\", t do\n      ref = WsClient.send_call(t.client_ws, \"user:get_following\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply(\"user:get_following:reply\", ref, %{\"following\" => []})\n    end\n\n    test \"returns that person if you are following someone\", t do\n      %{id: followed_id} = Factory.create(User)\n      Kousa.Follow.follow(t.user.id, followed_id, true)\n\n      ref = WsClient.send_call(t.client_ws, \"user:get_following\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply(\"user:get_following:reply\", ref, %{\n        \"following\" => [\n          %{\n            \"id\" => ^followed_id\n          }\n        ]\n      })\n    end\n\n    test \"returns that person if you are following someone including the current room they are in\",\n         t do\n      %{id: followed_id} = followed = Factory.create(User)\n      followed_ws = WsClientFactory.create_client_for(followed)\n      Kousa.Follow.follow(t.user.id, followed_id, true)\n\n      room_ref =\n        WsClient.send_call(\n          followed_ws,\n          \"room:create\",\n          %{\"name\" => \"foo room\", \"description\" => \"foo\"}\n        )\n\n      WsClient.assert_reply(\n        \"room:create:reply\",\n        room_ref,\n        %{\n          \"id\" => room_id\n        }\n      )\n\n      ref = WsClient.send_call(t.client_ws, \"user:get_following\", %{\"cursor\" => 0})\n\n      WsClient.assert_reply(\"user:get_following:reply\", ref, %{\n        \"following\" => [\n          %{\n            \"id\" => ^followed_id,\n            \"currentRoom\" => %{\"id\" => ^room_id}\n          }\n        ]\n      })\n    end\n\n    test \"can get following for someone else\", t do\n      %{id: follower_id, username: username} = Factory.create(User)\n      Kousa.Follow.follow(follower_id, t.user.id, true)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:get_following\", %{\n          \"cursor\" => 0,\n          \"username\" => username\n        })\n\n      user_id = t.user.id\n\n      WsClient.assert_reply(\"user:get_following:reply\", ref, %{\n        \"following\" => [\n          %{\n            \"id\" => ^user_id\n          }\n        ]\n      })\n    end\n\n    @tag :skip\n    test \"test proper pagination of user:get_following\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/get_info_test.exs",
    "content": "defmodule BrothTest.User.GetInfoTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:get_info operation\" do\n    test \"can get your own user info\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:get_info\",\n          %{\"userIdOrUsername\" => t.user.id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_info:reply\",\n        ref,\n        %{\n          \"id\" => ^user_id\n        }\n      )\n    end\n\n    test \"you get nil back for username that doesn't exist\", t do\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:get_info\",\n          %{\"userIdOrUsername\" => \"aosifdjoqwejfoq\"}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_info:reply\",\n        ref,\n        %{\"error\" => \"could not find user\"}\n      )\n    end\n\n    @tag :skip\n    test \"you can't stalk someone who has blocked you\"\n  end\n\n  describe \"doesn't return a user back\" do\n    test \"if the user blocked you\", t do\n      blocked = Factory.create(User)\n      blocked_ws = WsClientFactory.create_client_for(blocked)\n\n      WsClient.do_call(t.client_ws, \"user:block\", %{\"userId\" => blocked.id})\n\n      ref =\n        WsClient.send_call(\n          blocked_ws,\n          \"user:get_info\",\n          %{\"userIdOrUsername\" => t.user.id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_info:reply\",\n        ref,\n        %{\"error\" => \"blocked\"}\n      )\n\n      # username search results in the same\n      ref2 =\n        WsClient.send_call(\n          blocked_ws,\n          \"user:get_info\",\n          %{\"userIdOrUsername\" => t.user.username}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_info:reply\",\n        ref2,\n        %{\"error\" => \"blocked\"}\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/get_relationship_test.exs",
    "content": "defmodule BrothTest.User.GetRelationshipTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:get_relationship operation\" do\n    test \"retrieves symmetric following info\", t do\n      user_id = t.user.id\n\n      followed = %{id: followed_id} = Factory.create(User)\n      followed_ws = WsClientFactory.create_client_for(followed)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => \"self\"},\n        t.client_ws\n      )\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => followed_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => nil},\n        t.client_ws\n      )\n\n      ref =\n        WsClient.send_call(\n          followed_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => nil},\n        followed_ws\n      )\n\n      Kousa.Follow.follow(t.user.id, followed_id, true)\n      Kousa.Follow.follow(followed_id, t.user.id, true)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => followed_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => \"mutual\"},\n        t.client_ws\n      )\n\n      ref =\n        WsClient.send_call(\n          followed_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => \"mutual\"},\n        followed_ws\n      )\n    end\n\n    test \"retrieves asymmetric following info\", t do\n      user_id = t.user.id\n\n      followed = %{id: followed_id} = Factory.create(User)\n      followed_ws = WsClientFactory.create_client_for(followed)\n\n      Kousa.Follow.follow(t.user.id, followed_id, true)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => followed_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => \"following\"},\n        t.client_ws\n      )\n\n      ref =\n        WsClient.send_call(\n          followed_ws,\n          \"user:get_relationship\",\n          %{\"userId\" => user_id}\n        )\n\n      WsClient.assert_reply(\n        \"user:get_relationship:reply\",\n        ref,\n        %{\"relationship\" => \"follower\"},\n        followed_ws\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/revoke_api_key_test.exs",
    "content": "defmodule BrothTest.User.RevokeApiKeyTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the user:revoke_api_key operation\" do\n    test \"causes the api key to update\", t do\n      WsClient.do_call(t.client_ws, \"user:create_bot\", %{\n        \"username\" => \"marvin\"\n      })\n\n      %User{id: user_id, apiKey: api_key} = Beef.Users.get_by_username(\"marvin\")\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:revoke_api_key\", %{\n          \"userId\" => user_id\n        })\n\n      WsClient.assert_reply(\"user:revoke_api_key:reply\", ref, %{\"apiKey\" => _})\n\n      refute api_key == Beef.Users.get_by_username(\"marvin\").apiKey\n    end\n\n    test \"you need to own the bot to revoke the api key\", t do\n      WsClient.do_call(t.client_ws, \"user:create_bot\", %{\n        \"username\" => \"marvin\"\n      })\n\n      %User{id: user_id, apiKey: api_key} = Beef.Users.get_by_username(\"marvin\")\n\n      other_user = Factory.create(User)\n      other_client_ws = WsClientFactory.create_client_for(other_user)\n\n      ref =\n        WsClient.send_call(other_client_ws, \"user:revoke_api_key\", %{\n          \"userId\" => user_id\n        })\n\n      WsClient.assert_error(\"user:revoke_api_key\", ref, %{\"message\" => \"not authorized\"})\n\n      assert api_key == Beef.Users.get_by_username(\"marvin\").apiKey\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/unblock_test.exs",
    "content": "defmodule BrothTest.User.UnblockTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:unblock operation\" do\n    test \"unblocks that person from the user\", t do\n      # create a blocked user that is logged in.\n      %{id: blocked_id} = Factory.create(User)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:block\",\n          %{\"userId\" => blocked_id}\n        )\n\n      # assert that you get a response that is yourself with an updated\n      # block list.\n      WsClient.assert_reply(\n        \"user:block:reply\",\n        ref,\n        %{\"blocked\" => [^blocked_id]},\n        t.client_ws\n      )\n\n      ref2 =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:unblock\",\n          %{\"userId\" => blocked_id}\n        )\n\n      # assert that you get a response that is yourself with an updated\n      # block list.\n      WsClient.assert_reply(\n        \"user:unblock:reply\",\n        ref2,\n        %{},\n        t.client_ws\n      )\n\n      refute Beef.UserBlocks.blocked?(t.user.id, blocked_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/unfollow_test.exs",
    "content": "defmodule BrothTest.User.UnfollowTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the user:unfollow operation\" do\n    test \"causes you to to unfollow\", t do\n      followed = Factory.create(User)\n\n      Beef.Follows.insert(%{\n        userId: followed.id,\n        followerId: t.user.id\n      })\n\n      assert Beef.Follows.following_me?(followed.id, t.user.id)\n\n      ref =\n        WsClient.send_call(t.client_ws, \"user:unfollow\", %{\n          \"userId\" => followed.id\n        })\n\n      WsClient.assert_reply(\"user:unfollow:reply\", ref, %{})\n\n      refute Beef.Follows.following_me?(followed.id, t.user.id)\n    end\n\n    @tag :skip\n    test \"you can't follow yourself?\"\n  end\nend\n"
  },
  {
    "path": "kousa/test/broth/user/update_test.exs",
    "content": "defmodule BrothTest.User.UpdateTest do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias Beef.Users\n  alias BrothTest.WsClient\n  alias BrothTest.WsClientFactory\n  alias KousaTest.Support.Factory\n\n  require WsClient\n\n  setup do\n    user = Factory.create(User)\n    client_ws = WsClientFactory.create_client_for(user)\n\n    {:ok, user: user, client_ws: client_ws}\n  end\n\n  describe \"the websocket user:update operation\" do\n    test \"a username can be changed\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:update\",\n          %{\n            \"username\" => \"new_username\"\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:update:reply\",\n        ref,\n        %{\n          \"username\" => \"new_username\"\n        }\n      )\n\n      # and we will get a second reply, but that's for the case where\n      # there are multiple ws out for the same user.\n      WsClient.assert_frame(\n        \"user:update\",\n        %{\n          \"username\" => \"new_username\"\n        }\n      )\n\n      assert Users.get_by_id(user_id).username == \"new_username\"\n    end\n\n    test \"username taken\", t do\n      existing_username = \"oiqwjodjo\"\n      Factory.create(User, username: existing_username)\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:update\",\n          %{\n            \"username\" => existing_username\n          }\n        )\n\n      WsClient.assert_error(\"user:update\", ref, %{\"username\" => \"has already been taken\"})\n    end\n\n    test \"a bio,displayName,avatarUrl can be changed\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:update\",\n          %{\n            \"bio\" => \"hi\",\n            \"displayName\" => \"test\",\n            \"avatarUrl\" =>\n              \"https://pbs.twimg.com/profile_images/1152793238761345024/VRBvxeCM_400x400.jpg\"\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:update:reply\",\n        ref,\n        %{\n          \"bio\" => \"hi\",\n          \"displayName\" => \"test\",\n          \"avatarUrl\" =>\n            \"https://pbs.twimg.com/profile_images/1152793238761345024/VRBvxeCM_400x400.jpg\"\n        }\n      )\n\n      user = Users.get_by_id(user_id)\n\n      assert user.bio == \"hi\"\n      assert user.displayName == \"test\"\n\n      assert user.avatarUrl ==\n               \"https://pbs.twimg.com/profile_images/1152793238761345024/VRBvxeCM_400x400.jpg\"\n    end\n\n    test \"a bad avatar\", t do\n      user_id = t.user.id\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:update\",\n          %{\n            \"avatarUrl\" => \"https://bob.example.com/\"\n          }\n        )\n\n      WsClient.assert_error(\"user:update\", ref, %{\"avatarUrl\" => \"has invalid format\"})\n\n      user = Users.get_by_id(user_id)\n\n      assert user.avatarUrl == t.user.avatarUrl\n    end\n\n    @tag :skip\n    test \"when you have two websockets connected updating one propagates change to other\"\n\n    @tag :skip\n    test \"bad usernames\"\n\n    @tag :skip\n    test \"other fields\"\n  end\n\n  describe \"update banner url twitter variations\" do\n    test \"no extension works\", t do\n      user_id = t.user.id\n      url = \"https://pbs.twimg.com/profile_banners/1241894903472427015/1587079476\"\n\n      ref =\n        WsClient.send_call(\n          t.client_ws,\n          \"user:update\",\n          %{\n            \"bannerUrl\" => url\n          }\n        )\n\n      WsClient.assert_reply(\n        \"user:update:reply\",\n        ref,\n        %{\n          \"bannerUrl\" => url\n        }\n      )\n\n      user = Users.get_by_id(user_id)\n\n      assert user.bannerUrl ==\n               url\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/kousa/user.ex",
    "content": "defmodule KousaTest.User do\n  use ExUnit.Case, async: true\n  use KousaTest.Support.EctoSandbox\n\n  alias Beef.Schemas.User\n  alias KousaTest.Support.Factory\n  alias Onion.PubSub\n\n  setup do\n    user = Factory.create(User)\n    {:ok, user: user}\n  end\n\n  describe \"update_with/1\" do\n    test \"updates a user with the changeset\", %{user: user = %{id: user_id}} do\n      PubSub.subscribe(\"user:update:\" <> user_id)\n\n      user\n      |> Ecto.Changeset.cast(%{username: \"foobar\"}, [:username])\n      |> Kousa.User.update_with()\n\n      # and propagates new user info to the PubSub notification channel.\n      assert_receive {\"user:update:\" <> ^user_id, %{username: \"foobar\"}}\n\n      assert %{username: \"foobar\"} = Beef.Users.get(user_id)\n    end\n  end\nend\n"
  },
  {
    "path": "kousa/test/test_helper.exs",
    "content": "ExUnit.start()\n\nEcto.Adapters.SQL.Sandbox.mode(Beef.Repo, :manual)\n\n# start up a process pool for making requests\nFinch.start_link(name: BrothHttpRequests)\n\ndefmodule KousaTest do\n  def elixir_module?(module) do\n    module\n    |> Atom.to_string()\n    |> String.starts_with?(\"Elixir\")\n  end\n\n  @classes ~w(User Room Chat Auth)\n  def message_module?(module) do\n    exploded = Module.split(module)\n    match?([\"Broth\", \"Message\", class, _] when class in @classes, exploded)\n  end\n\n  def message_validation_module?(module) do\n    exploded = Module.split(module)\n    match?([\"BrothTest\", \"Message\", class, _] when class in @classes, exploded)\n  end\n\n  def message_test_module?(module) do\n    exploded = Module.split(module)\n    match?([\"BrothTest\", class, _] when class in @classes, exploded)\n  end\n\n  def test_for(module) do\n    [\"Broth\", \"Message\", class, type] = Module.split(module)\n    Module.concat([\"BrothTest\", class, type <> \"Test\"])\n  end\n\n  def validation_for(module) do\n    [\"Broth\", \"Message\", class, type] = Module.split(module)\n    Module.concat([\"BrothTest\", \"Message\", class, type <> \"Test\"])\n  end\nend\n\nif System.argv() == [\"test\"] do\n  # this check only gets triggered when you do a full test\n\n  ExUnit.after_suite(fn _ ->\n    # check to make sure all of the modules have corresponding tests\n    all_elixir_modules =\n      :code.all_loaded()\n      |> Enum.map(&elem(&1, 0))\n      |> Enum.filter(&KousaTest.elixir_module?/1)\n\n    message_modules = Enum.filter(all_elixir_modules, &KousaTest.message_module?/1)\n\n    message_test_modules = Enum.filter(all_elixir_modules, &KousaTest.message_test_module?/1)\n\n    message_validation_modules =\n      Enum.filter(all_elixir_modules, &KousaTest.message_validation_module?/1)\n\n    Enum.each(message_modules, fn module ->\n      unless (tm = KousaTest.test_for(module)) in message_test_modules do\n        raise \"#{inspect(module)} did not have test module #{inspect(tm)}\"\n      end\n\n      unless (tm = KousaTest.validation_for(module)) in message_validation_modules do\n        raise \"#{inspect(module)} did not have validation module #{inspect(tm)}\"\n      end\n    end)\n  end)\nend\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"dogehouse\",\n  \"private\": true,\n  \"workspaces\": {\n    \"packages\": [\n      \"kebab\",\n      \"kibbeh\",\n      \"dinner\",\n      \"dolma\"\n    ],\n    \"nohoist\": [\n      \"**\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^12.1.1\",\n    \"@commitlint/config-conventional\": \"^12.1.1\",\n    \"@types/jest\": \"^26.0.23\",\n    \"commitizen\": \"^4.2.3\",\n    \"cz-conventional-changelog\": \"^3.3.0\",\n    \"husky\": \"^6.0.0\",\n    \"mediasoup-client\": \"^3.6.30\",\n    \"mediasoup-client-aiortc\": \"^3.6.3\",\n    \"ts-jest\": \"^26.5.5\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\",\n      \"defaultScope\": \"global\"\n    }\n  },\n  \"scripts\": {\n    \"prepare\": \"husky install\",\n    \"commit\": \"cz\",\n    \"commit:signed\": \"cz -S\"\n  }\n}\n"
  },
  {
    "path": "pilaf/.buckconfig",
    "content": "\n[android]\n  target = Google Inc.:Google APIs:23\n\n[maven_repositories]\n  central = https://repo1.maven.org/maven2\n"
  },
  {
    "path": "pilaf/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: \"@react-native-community\",\n  rules: {\n    quotes: [2, \"double\", { avoidEscape: true }],\n    semi: [2, \"always\"],\n    \"react-native/no-inline-styles\": 0,\n  },\n};\n"
  },
  {
    "path": "pilaf/.flowconfig",
    "content": "[ignore]\n; We fork some components by platform\n.*/*[.]android.js\n\n; Ignore \"BUCK\" generated dirs\n<PROJECT_ROOT>/\\.buckd/\n\n; Ignore polyfills\nnode_modules/react-native/Libraries/polyfills/.*\n\n; These should not be required directly\n; require from fbjs/lib instead: require('fbjs/lib/warning')\nnode_modules/warning/.*\n\n; Flow doesn't support platforms\n.*/Libraries/Utilities/LoadingView.js\n\n[untyped]\n.*/node_modules/@react-native-community/cli/.*/.*\n\n[include]\n\n[libs]\nnode_modules/react-native/interface.js\nnode_modules/react-native/flow/\n\n[options]\nemoji=true\n\nesproposal.optional_chaining=enable\nesproposal.nullish_coalescing=enable\n\nmodule.file_ext=.js\nmodule.file_ext=.json\nmodule.file_ext=.ios.js\n\nmunge_underscores=true\n\nmodule.name_mapper='^react-native/\\(.*\\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\\1'\nmodule.name_mapper='^@?[./a-zA-Z0-9$_-]+\\.\\(bmp\\|gif\\|jpg\\|jpeg\\|png\\|psd\\|svg\\|webp\\|m4v\\|mov\\|mp4\\|mpeg\\|mpg\\|webm\\|aac\\|aiff\\|caf\\|m4a\\|mp3\\|wav\\|html\\|pdf\\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'\n\nsuppress_type=$FlowIssue\nsuppress_type=$FlowFixMe\nsuppress_type=$FlowFixMeProps\nsuppress_type=$FlowFixMeState\n\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowFixMe\\\\($\\\\|[^(]\\\\|(\\\\(<VERSION>\\\\)? *\\\\(site=[a-z,_]*react_native\\\\(_ios\\\\)?_\\\\(oss\\\\|fb\\\\)[a-z,_]*\\\\)?)\\\\)\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowIssue\\\\((\\\\(<VERSION>\\\\)? *\\\\(site=[a-z,_]*react_native\\\\(_ios\\\\)?_\\\\(oss\\\\|fb\\\\)[a-z,_]*\\\\)?)\\\\)?:? #[0-9]+\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowExpectedError\n\n[lints]\nsketchy-null-number=warn\nsketchy-null-mixed=warn\nsketchy-number=warn\nuntyped-type-import=warn\nnonstrict-import=warn\ndeprecated-type=warn\nunsafe-getters-setters=warn\nunnecessary-invariant=warn\nsignature-verification-failure=warn\ndeprecated-utility=error\n\n[strict]\ndeprecated-type\nnonstrict-import\nsketchy-null\nunclear-type\nunsafe-getters-setters\nuntyped-import\nuntyped-type-import\n\n[version]\n^0.122.0\n"
  },
  {
    "path": "pilaf/.gitattributes",
    "content": "*.pbxproj -text\n"
  },
  {
    "path": "pilaf/.gitignore",
    "content": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\n\n# Android/IntelliJ\n#\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n\n# node.js\n#\nnode_modules/\nnpm-debug.log\nyarn-error.log\n\n# BUCK\nbuck-out/\n\\.buckd/\n*.keystore\n!debug.keystore\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/\n\n*/fastlane/report.xml\n*/fastlane/Preview.html\n*/fastlane/screenshots\n\n# Bundle artifact\n*.jsbundle\n\n# CocoaPods\n/ios/Pods/\n\n.yarn/*\n!.yarn/releases\n!.yarn/plugins\n!.yarn/sdks\n!.yarn/versions\n.pnp.*\n\n\n"
  },
  {
    "path": "pilaf/.prettierrc.js",
    "content": "module.exports = {\n  trailingComma: \"es5\",\n  tabWidth: 2,\n  semi: true,\n  singleQuote: false,\n  arrowParens: \"always\",\n  bracketSpacing: true,\n};\n"
  },
  {
    "path": "pilaf/.storybook/main.js",
    "content": "module.exports = {\n  stories: [\"../src/stories/**/*.story.@(ts|tsx|js|jsx|mdx)\"],\n  addons: [\"@storybook/addon-essentials\"],\n}\n"
  },
  {
    "path": "pilaf/.storybook/manager.js",
    "content": "import { addons } from \"@storybook/addons\"\nimport { create } from \"@storybook/theming\"\n\naddons.setConfig({\n  theme: create({\n    base: \"dark\",\n    brandTitle: \"DogeBook\",\n  }),\n})\n"
  },
  {
    "path": "pilaf/.watchmanconfig",
    "content": "{}\n"
  },
  {
    "path": "pilaf/.yarnrc.yml",
    "content": "nodeLinker: node-modules\n\nplugins:\n  - path: ../.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs\n    spec: \"@yarnpkg/plugin-workspace-tools\"\n  - path: ../.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs\n    spec: \"@yarnpkg/plugin-interactive-tools\"\n\nyarnPath: ../.yarn/releases/yarn-berry.cjs\n"
  },
  {
    "path": "pilaf/App.tsx",
    "content": "/**\n * Sample React Native App\n * https://github.com/facebook/react-native\n *\n * @format\n * @flow strict-local\n */\nimport { LinkingOptions, NavigationContainer } from \"@react-navigation/native\";\nimport React, { useEffect } from \"react\";\nimport { LogBox, StatusBar } from \"react-native\";\nimport \"react-native-gesture-handler\";\nimport \"react-native-get-random-values\";\nimport { SafeAreaProvider } from \"react-native-safe-area-context\";\nimport SplashScreen from \"react-native-splash-screen\";\nimport Toast from \"react-native-toast-message\";\nimport { registerGlobals } from \"react-native-webrtc\";\nimport { QueryClientProvider } from \"react-query\";\nimport { colors } from \"./src/constants/dogeStyle\";\nimport { queryClient } from \"./src/lib/queryClient\";\nimport { useTokenStore } from \"./src/modules/auth/useTokenStore\";\nimport { useSoundEffectStore } from \"./src/modules/sound-effect/useSoundEffectStore\";\nimport { useVoiceStore } from \"./src/modules/webrtc/stores/useVoiceStore\";\nimport { WebSocketProvider } from \"./src/modules/ws/WebSocketProvider\";\nimport { AuthenticationSwitch } from \"./src/navigation/AuthenticationSwitch\";\nimport { navigationRef } from \"./src/navigation/RootNavigation\";\nimport { MainWsHandlerProvider } from \"./src/shared-hooks/useMainWsHandler\";\n\nLogBox.ignoreLogs([\"Setting a timer\"]);\n\nconst App: React.FC = () => {\n  registerGlobals();\n\n  const loadTokens = useTokenStore((state) => state.loadTokens);\n  useSoundEffectStore();\n  const isTokenStoreReady = useTokenStore(\n    (s) => s.accessToken !== undefined && s.refreshToken !== undefined\n  );\n  if (!isTokenStoreReady) {\n    loadTokens();\n  }\n\n  useEffect(() => {\n    if (isTokenStoreReady) {\n      SplashScreen.hide();\n    }\n  }, [isTokenStoreReady]);\n\n  const isVoicePrepared = useVoiceStore((s) => s.device !== undefined);\n  const prepare = useVoiceStore((state) => state.prepare);\n  if (!isVoicePrepared) {\n    prepare();\n  }\n\n  const deepLinksConf = {\n    screens: {\n      Room: \"room/:roomId\",\n    },\n  };\n\n  const linking: LinkingOptions = {\n    prefixes: [\"dogehouse://\", \"https://next.dogehouse.tv\"],\n    config: deepLinksConf,\n  };\n\n  return (\n    <WebSocketProvider shouldConnect={true}>\n      <QueryClientProvider client={queryClient}>\n        <MainWsHandlerProvider />\n        <SafeAreaProvider>\n          <NavigationContainer ref={navigationRef} linking={linking}>\n            <StatusBar\n              barStyle=\"light-content\"\n              backgroundColor={colors.primary900}\n            />\n            <AuthenticationSwitch />\n            <Toast ref={(ref) => Toast.setRef(ref)} />\n          </NavigationContainer>\n        </SafeAreaProvider>\n      </QueryClientProvider>\n    </WebSocketProvider>\n  );\n};\n\nexport default App;\n"
  },
  {
    "path": "pilaf/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Emin Khateeb\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "pilaf/README.md",
    "content": "<p align=\"center\">\n    <img height=100 src=\"https://raw.githubusercontent.com/benawad/dogehouse/staging/.redesign-assets/dogehouse_logo.svg\"/>\n</p>\n\n<p align=\"center\">\n    <strong>Taking voice conversations to the moon 🚀</strong>\n</p>\n\n<p align=\"center\">\n    <img src=\"https://img.shields.io/github/contributors/benawad/dogehouse\"/>\n    <img src=\"https://img.shields.io/discord/810571477316403233?label=discord\"/>\n    <img src=\"https://img.shields.io/github/v/release/benawad/dogehouse\"/>\n</p>\n<br/>\n\n# What is this folder?\n\nThis folder is pilaf ([pi·laf](https://en.wikipedia.org/wiki/Pilaf)) and it contains everything related to the [React Native](https://reactnative.dev/) app.\n\n# How can I contribute?\n\nFirst of all, this project is currently in _very_ early stages of development, therefore these instructions may not be up to date.\n\n**We're using [Yarn](https://yarnpkg.com/) for this project, do not use npm for the following commands**\n\nCompile @dogehouse/kebab by executing the following commands:\n\n```bash\ncd ../kebab\nyarn\nyarn build\n```\n\nAfter you successfully compiled Kebab, go back to this directory and install all modules (@dogehouse/kebab is a yarn workspace, you do <u>not</u> need to manually copy it to node_modules)\n\nYou should now be all set to go, go ahead and run the dev server\n\n```bash\ncd ../pilaf\nyarn\nyarn start\n```\n\nYou now have a metro bundler running, you can start the app on Android or iOS\n"
  },
  {
    "path": "pilaf/__tests__/App-test.js",
    "content": "/**\n * @format\n */\n\nimport 'react-native';\nimport React from 'react';\nimport App from '../App';\n\n// Note: test renderer must be required after react-native.\nimport renderer from 'react-test-renderer';\n\nit('renders correctly', () => {\n\trenderer.create(<App />);\n});\n"
  },
  {
    "path": "pilaf/android/app/BUCK",
    "content": "# To learn about Buck see [Docs](https://buckbuild.com/).\n# To run your application with Buck:\n# - install Buck\n# - `npm start` - to start the packager\n# - `cd android`\n# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname \"CN=Android Debug,O=Android,C=US\"`\n# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck\n# - `buck install -r android/app` - compile, install and run application\n#\n\nload(\":build_defs.bzl\", \"create_aar_targets\", \"create_jar_targets\")\n\nlib_deps = []\n\ncreate_aar_targets(glob([\"libs/*.aar\"]))\n\ncreate_jar_targets(glob([\"libs/*.jar\"]))\n\nandroid_library(\n    name = \"all-libs\",\n    exported_deps = lib_deps,\n)\n\nandroid_library(\n    name = \"app-code\",\n    srcs = glob([\n        \"src/main/java/**/*.java\",\n    ]),\n    deps = [\n        \":all-libs\",\n        \":build_config\",\n        \":res\",\n    ],\n)\n\nandroid_build_config(\n    name = \"build_config\",\n    package = \"com.rice\",\n)\n\nandroid_resource(\n    name = \"res\",\n    package = \"com.rice\",\n    res = \"src/main/res\",\n)\n\nandroid_binary(\n    name = \"app\",\n    keystore = \"//android/keystores:debug\",\n    manifest = \"src/main/AndroidManifest.xml\",\n    package_type = \"debug\",\n    deps = [\n        \":app-code\",\n    ],\n)\n"
  },
  {
    "path": "pilaf/android/app/build.gradle",
    "content": "apply plugin: \"com.android.application\"\napply from: project(':react-native-config').projectDir.getPath() + \"/dotenv.gradle\"\napply from: \"../../node_modules/react-native-vector-icons/fonts.gradle\"\nimport com.android.build.OutputFile\n\n/**\n * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets\n * and bundleReleaseJsAndAssets).\n * These basically call `react-native bundle` with the correct arguments during the Android build\n * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the\n * bundle directly from the development server. Below you can see all the possible configurations\n * and their defaults. If you decide to add a configuration block, make sure to add it before the\n * `apply from: \"../../node_modules/react-native/react.gradle\"` line.\n *\n * project.ext.react = [\n *   // the name of the generated asset file containing your JS bundle\n *   bundleAssetName: \"index.android.bundle\",\n *\n *   // the entry file for bundle generation. If none specified and\n *   // \"index.android.js\" exists, it will be used. Otherwise \"index.js\" is\n *   // default. Can be overridden with ENTRY_FILE environment variable.\n *   entryFile: \"index.android.js\",\n *\n *   // https://reactnative.dev/docs/performance#enable-the-ram-format\n *   bundleCommand: \"ram-bundle\",\n *\n *   // whether to bundle JS and assets in debug mode\n *   bundleInDebug: false,\n *\n *   // whether to bundle JS and assets in release mode\n *   bundleInRelease: true,\n *\n *   // whether to bundle JS and assets in another build variant (if configured).\n *   // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants\n *   // The configuration property can be in the following formats\n *   //         'bundleIn${productFlavor}${buildType}'\n *   //         'bundleIn${buildType}'\n *   // bundleInFreeDebug: true,\n *   // bundleInPaidRelease: true,\n *   // bundleInBeta: true,\n *\n *   // whether to disable dev mode in custom build variants (by default only disabled in release)\n *   // for example: to disable dev mode in the staging build type (if configured)\n *   devDisabledInStaging: true,\n *   // The configuration property can be in the following formats\n *   //         'devDisabledIn${productFlavor}${buildType}'\n *   //         'devDisabledIn${buildType}'\n *\n *   // the root of your project, i.e. where \"package.json\" lives\n *   root: \"../../\",\n *\n *   // where to put the JS bundle asset in debug mode\n *   jsBundleDirDebug: \"$buildDir/intermediates/assets/debug\",\n *\n *   // where to put the JS bundle asset in release mode\n *   jsBundleDirRelease: \"$buildDir/intermediates/assets/release\",\n *\n *   // where to put drawable resources / React Native assets, e.g. the ones you use via\n *   // require('./image.png')), in debug mode\n *   resourcesDirDebug: \"$buildDir/intermediates/res/merged/debug\",\n *\n *   // where to put drawable resources / React Native assets, e.g. the ones you use via\n *   // require('./image.png')), in release mode\n *   resourcesDirRelease: \"$buildDir/intermediates/res/merged/release\",\n *\n *   // by default the gradle tasks are skipped if none of the JS files or assets change; this means\n *   // that we don't look at files in android/ or ios/ to determine whether the tasks are up to\n *   // date; if you have any other folders that you want to ignore for performance reasons (gradle\n *   // indexes the entire tree), add them here. Alternatively, if you have JS files in android/\n *   // for example, you might want to remove it from here.\n *   inputExcludes: [\"android/**\", \"ios/**\"],\n *\n *   // override which node gets called and with what additional arguments\n *   nodeExecutableAndArgs: [\"node\"],\n *\n *   // supply additional arguments to the packager\n *   extraPackagerArgs: []\n * ]\n */\n\nproject.ext.react = [\n    enableHermes: false,  // clean and rebuild if changing\n]\n\napply from: \"../../node_modules/react-native/react.gradle\"\n\n/**\n * Set this to true to create two separate APKs instead of one:\n *   - An APK that only works on ARM devices\n *   - An APK that only works on x86 devices\n * The advantage is the size of the APK is reduced by about 4MB.\n * Upload all the APKs to the Play Store and people will download\n * the correct one based on the CPU architecture of their device.\n */\ndef enableSeparateBuildPerCPUArchitecture = false\n\n/**\n * Run Proguard to shrink the Java bytecode in release builds.\n */\ndef enableProguardInReleaseBuilds = false\n\n/**\n * The preferred build flavor of JavaScriptCore.\n *\n * For example, to use the international variant, you can use:\n * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`\n *\n * The international variant includes ICU i18n library and necessary data\n * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that\n * give correct results when using with locales other than en-US.  Note that\n * this variant is about 6MiB larger per architecture than default.\n */\ndef jscFlavor = 'org.webkit:android-jsc:+'\n\n/**\n * Whether to enable the Hermes VM.\n *\n * This should be set on project.ext.react and mirrored here.  If it is not set\n * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode\n * and the benefits of using Hermes will therefore be sharply reduced.\n */\ndef enableHermes = project.ext.react.get(\"enableHermes\", false);\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    defaultConfig {\n        applicationId \"com.rice\"\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n    }\n    splits {\n        abi {\n            reset()\n            enable enableSeparateBuildPerCPUArchitecture\n            universalApk false  // If true, also generate a universal APK\n            include \"armeabi-v7a\", \"x86\", \"arm64-v8a\", \"x86_64\"\n        }\n    }\n    signingConfigs {\n        debug {\n            storeFile file('debug.keystore')\n            storePassword 'android'\n            keyAlias 'androiddebugkey'\n            keyPassword 'android'\n        }\n    }\n    buildTypes {\n        debug {\n            signingConfig signingConfigs.debug\n        }\n        release {\n            // Caution! In production, you need to generate your own keystore file.\n            // see https://reactnative.dev/docs/signed-apk-android.\n            signingConfig signingConfigs.debug\n            minifyEnabled enableProguardInReleaseBuilds\n            proguardFiles getDefaultProguardFile(\"proguard-android.txt\"), \"proguard-rules.pro\"\n        }\n    }\n\n    // applicationVariants are e.g. debug, release\n    applicationVariants.all { variant ->\n        variant.outputs.each { output ->\n            // For each separate APK per architecture, set a unique version code as described here:\n            // https://developer.android.com/studio/build/configure-apk-splits.html\n            def versionCodes = [\"armeabi-v7a\": 1, \"x86\": 2, \"arm64-v8a\": 3, \"x86_64\": 4]\n            def abi = output.getFilter(OutputFile.ABI)\n            if (abi != null) {  // null for the universal-debug, universal-release variants\n                output.versionCodeOverride =\n                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode\n            }\n\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: \"libs\", include: [\"*.jar\"])\n    //noinspection GradleDynamicVersion\n    implementation \"com.facebook.react:react-native:+\"  // From node_modules\n\n    implementation \"androidx.swiperefreshlayout:swiperefreshlayout:1.0.0\"\n\n    debugImplementation(\"com.facebook.flipper:flipper:${FLIPPER_VERSION}\") {\n      exclude group:'com.facebook.fbjni'\n    }\n\n    debugImplementation(\"com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}\") {\n        exclude group:'com.facebook.flipper'\n        exclude group:'com.squareup.okhttp3', module:'okhttp'\n    }\n\n    debugImplementation(\"com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}\") {\n        exclude group:'com.facebook.flipper'\n    }\n\n    if (enableHermes) {\n        def hermesPath = \"../../node_modules/hermes-engine/android/\";\n        debugImplementation files(hermesPath + \"hermes-debug.aar\")\n        releaseImplementation files(hermesPath + \"hermes-release.aar\")\n    } else {\n        implementation jscFlavor\n    }\n}\n\n// Run this once to be able to run the application with BUCK\n// puts all compile dependencies into folder libs for BUCK to use\ntask copyDownloadableDepsToLibs(type: Copy) {\n    from configurations.compile\n    into 'libs'\n}\n\napply from: file(\"../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle\"); applyNativeModulesAppBuildGradle(project)\n"
  },
  {
    "path": "pilaf/android/app/build_defs.bzl",
    "content": "\"\"\"Helper definitions to glob .aar and .jar targets\"\"\"\n\ndef create_aar_targets(aarfiles):\n    for aarfile in aarfiles:\n        name = \"aars__\" + aarfile[aarfile.rindex(\"/\") + 1:aarfile.rindex(\".aar\")]\n        lib_deps.append(\":\" + name)\n        android_prebuilt_aar(\n            name = name,\n            aar = aarfile,\n        )\n\ndef create_jar_targets(jarfiles):\n    for jarfile in jarfiles:\n        name = \"jars__\" + jarfile[jarfile.rindex(\"/\") + 1:jarfile.rindex(\".jar\")]\n        lib_deps.append(\":\" + name)\n        prebuilt_jar(\n            name = name,\n            binary_jar = jarfile,\n        )\n"
  },
  {
    "path": "pilaf/android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n"
  },
  {
    "path": "pilaf/android/app/src/debug/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>\n\n    <application android:usesCleartextTraffic=\"true\" tools:targetApi=\"28\" tools:ignore=\"GoogleAppIndexingWarning\" />\n</manifest>\n"
  },
  {
    "path": "pilaf/android/app/src/debug/java/com/rice/ReactNativeFlipper.java",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * <p>This source code is licensed under the MIT license found in the LICENSE file in the root\n * directory of this source tree.\n */\npackage com.rice;\n\nimport android.content.Context;\nimport com.facebook.flipper.android.AndroidFlipperClient;\nimport com.facebook.flipper.android.utils.FlipperUtils;\nimport com.facebook.flipper.core.FlipperClient;\nimport com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;\nimport com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;\nimport com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\nimport com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;\nimport com.facebook.flipper.plugins.network.NetworkFlipperPlugin;\nimport com.facebook.flipper.plugins.react.ReactFlipperPlugin;\nimport com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;\nimport com.facebook.react.ReactInstanceManager;\nimport com.facebook.react.bridge.ReactContext;\nimport com.facebook.react.modules.network.NetworkingModule;\nimport okhttp3.OkHttpClient;\n\npublic class ReactNativeFlipper {\n  public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {\n    if (FlipperUtils.shouldEnableFlipper(context)) {\n      final FlipperClient client = AndroidFlipperClient.getInstance(context);\n\n      client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));\n      client.addPlugin(new ReactFlipperPlugin());\n      client.addPlugin(new DatabasesFlipperPlugin(context));\n      client.addPlugin(new SharedPreferencesFlipperPlugin(context));\n      client.addPlugin(CrashReporterPlugin.getInstance());\n\n      NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();\n      NetworkingModule.setCustomClientBuilder(\n          new NetworkingModule.CustomClientBuilder() {\n            @Override\n            public void apply(OkHttpClient.Builder builder) {\n              builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));\n            }\n          });\n      client.addPlugin(networkFlipperPlugin);\n      client.start();\n\n      // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized\n      // Hence we run if after all native modules have been initialized\n      ReactContext reactContext = reactInstanceManager.getCurrentReactContext();\n      if (reactContext == null) {\n        reactInstanceManager.addReactInstanceEventListener(\n            new ReactInstanceManager.ReactInstanceEventListener() {\n              @Override\n              public void onReactContextInitialized(ReactContext reactContext) {\n                reactInstanceManager.removeReactInstanceEventListener(this);\n                reactContext.runOnNativeModulesQueueThread(\n                    new Runnable() {\n                      @Override\n                      public void run() {\n                        client.addPlugin(new FrescoFlipperPlugin());\n                      }\n                    });\n              }\n            });\n      } else {\n        client.addPlugin(new FrescoFlipperPlugin());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "pilaf/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  package=\"com.rice\">\n\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.BLUETOOTH\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <application\n      android:name=\".MainApplication\"\n      android:label=\"@string/app_name\"\n      android:icon=\"@mipmap/ic_launcher\"\n      android:roundIcon=\"@mipmap/ic_launcher_round\"\n      android:allowBackup=\"false\"\n      android:theme=\"@style/AppTheme\">\n      <activity\n        android:name=\".SplashActivity\"\n        android:theme=\"@style/SplashTheme\"\n        android:label=\"@string/app_name\">\n        <intent-filter>\n          <action android:name=\"android.intent.action.MAIN\" />\n          <category android:name=\"android.intent.category.LAUNCHER\" />\n        </intent-filter>\n      </activity>\n      <activity\n        android:name=\".MainActivity\"\n        android:exported=\"true\"\n        android:label=\"@string/app_name\"\n        android:configChanges=\"keyboard|keyboardHidden|orientation|screenSize|uiMode\"\n        android:launchMode=\"singleTask\"\n        android:windowSoftInputMode=\"adjustResize\">\n        <intent-filter>\n          <action android:name=\"android.intent.action.VIEW\" />\n          <category android:name=\"android.intent.category.DEFAULT\" />\n          <category android:name=\"android.intent.category.BROWSABLE\" />\n          <!-- Accepts URIs that begin with \"example://gizmos” -->\n          <data android:scheme=\"dogehouse\"/>\n        </intent-filter>\n      </activity>\n      <activity android:name=\"com.facebook.react.devsupport.DevSettingsActivity\" />\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "pilaf/android/app/src/main/java/com/rice/MainActivity.java",
    "content": "package com.rice;\n\nimport android.os.Bundle;\n\nimport com.facebook.react.ReactActivity;\n\nimport org.devio.rn.splashscreen.SplashScreen;\n\npublic class MainActivity extends ReactActivity {\n\n  /**\n   * Returns the name of the main component registered from JavaScript. This is used to schedule\n   * rendering of the component.\n   */\n  @Override\n  protected String getMainComponentName() {\n    return \"rice\";\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    SplashScreen.show(this);\n    super.onCreate(savedInstanceState);\n  }\n}\n"
  },
  {
    "path": "pilaf/android/app/src/main/java/com/rice/MainApplication.java",
    "content": "package com.rice;\n\nimport android.app.Application;\nimport android.content.Context;\nimport com.facebook.react.PackageList;\nimport com.facebook.react.ReactApplication;\nimport com.facebook.react.ReactInstanceManager;\nimport com.facebook.react.ReactNativeHost;\nimport com.facebook.react.ReactPackage;\nimport com.facebook.soloader.SoLoader;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.List;\n\npublic class MainApplication extends Application implements ReactApplication {\n\n  private final ReactNativeHost mReactNativeHost =\n      new ReactNativeHost(this) {\n        @Override\n        public boolean getUseDeveloperSupport() {\n          return BuildConfig.DEBUG;\n        }\n\n        @Override\n        protected List<ReactPackage> getPackages() {\n          @SuppressWarnings(\"UnnecessaryLocalVariable\")\n          List<ReactPackage> packages = new PackageList(this).getPackages();\n          // Packages that cannot be autolinked yet can be added manually here, for example:\n          // packages.add(new MyReactNativePackage());\n          return packages;\n        }\n\n        @Override\n        protected String getJSMainModuleName() {\n          return \"index\";\n        }\n      };\n\n  @Override\n  public ReactNativeHost getReactNativeHost() {\n    return mReactNativeHost;\n  }\n\n  @Override\n  public void onCreate() {\n    super.onCreate();\n    SoLoader.init(this, /* native exopackage */ false);\n    initializeFlipper(this, getReactNativeHost().getReactInstanceManager());\n  }\n\n  /**\n   * Loads Flipper in React Native templates. Call this in the onCreate method with something like\n   * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());\n   *\n   * @param context\n   * @param reactInstanceManager\n   */\n  private static void initializeFlipper(\n      Context context, ReactInstanceManager reactInstanceManager) {\n    if (BuildConfig.DEBUG) {\n      try {\n        /*\n         We use reflection here to pick up the class that initializes Flipper,\n        since Flipper library is not available in release mode\n        */\n        Class<?> aClass = Class.forName(\"com.rice.ReactNativeFlipper\");\n        aClass\n            .getMethod(\"initializeFlipper\", Context.class, ReactInstanceManager.class)\n            .invoke(null, context, reactInstanceManager);\n      } catch (ClassNotFoundException e) {\n        e.printStackTrace();\n      } catch (NoSuchMethodException e) {\n        e.printStackTrace();\n      } catch (IllegalAccessException e) {\n        e.printStackTrace();\n      } catch (InvocationTargetException e) {\n        e.printStackTrace();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "pilaf/android/app/src/main/java/com/rice/SplashActivity.java",
    "content": "package com.rice;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\n\npublic class SplashActivity extends AppCompatActivity {\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    setContentView(R.layout.launch_screen);\n\n    Intent intent = new Intent(this, MainActivity.class);\n    startActivity(intent);\n    finish();\n  }\n}\n"
  },
  {
    "path": "pilaf/android/app/src/main/res/drawable/background_splash.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <item\n    android:drawable=\"@color/splashscreen_bg\"/>\n\n  <item\n    android:width=\"300dp\"\n    android:height=\"300dp\"\n    android:drawable=\"@mipmap/splash_icon\"\n    android:gravity=\"center\" />\n\n</layer-list>\n"
  },
  {
    "path": "pilaf/android/app/src/main/res/layout/launch_screen.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@drawable/background_splash\"\n  android:orientation=\"vertical\">\n</LinearLayout>\n"
  },
  {
    "path": "pilaf/android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"splashscreen_bg\">#0b0e11</color>\n</resources>\n"
  },
  {
    "path": "pilaf/android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Dogehouse</string>\n</resources>\n"
  },
  {
    "path": "pilaf/android/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"android:textColor\">#000000</item>\n    </style>\n\n    <style name=\"SplashTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n      <item name=\"android:statusBarColor\">@color/splashscreen_bg</item>\n      <item name=\"android:background\">@drawable/background_splash</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "pilaf/android/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext {\n        buildToolsVersion = \"29.0.2\"\n        minSdkVersion = 21\n        compileSdkVersion = 30\n        targetSdkVersion = 30\n    }\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath(\"com.android.tools.build:gradle:3.5.3\")\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        mavenLocal()\n        maven {\n            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm\n            url(\"$rootDir/../node_modules/react-native/android\")\n        }\n        maven {\n            // Android JSC is installed from npm\n            url(\"$rootDir/../node_modules/jsc-android/dist\")\n        }\n\n        google()\n        jcenter()\n        maven { url 'https://www.jitpack.io' }\n    }\n}\n"
  },
  {
    "path": "pilaf/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.3-all.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "pilaf/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n\n# This one fixes a weird WebRTC runtime problem on some devices.\n# https://github.com/jitsi/jitsi-meet/issues/7911#issuecomment-714323255\nandroid.enableDexingArtifactTransform.desugaring=false\n\n# Version of flipper SDK to use with React Native\nFLIPPER_VERSION=0.80.0\n"
  },
  {
    "path": "pilaf/android/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "pilaf/android/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "pilaf/android/settings.gradle",
    "content": "rootProject.name = 'rice'\napply from: file(\"../node_modules/@react-native-community/cli-platform-android/native_modules.gradle\"); applyNativeModulesSettingsGradle(settings)\ninclude ':app'\n"
  },
  {
    "path": "pilaf/app.json",
    "content": "{\n  \"name\": \"rice\",\n  \"displayName\": \"Dogehouse\"\n}\n"
  },
  {
    "path": "pilaf/babel.config.js",
    "content": "module.exports = {\n  presets: [\"module:metro-react-native-babel-preset\"],\n};\n"
  },
  {
    "path": "pilaf/index.js",
    "content": "/**\n * @format\n */\n\nimport { AppRegistry } from \"react-native\";\nimport { name as appName } from \"./app.json\";\n\nimport { Config } from \"react-native-config\";\n\nimport App from \"./App\";\nimport StorybookUI from \"./storybook\";\n\nimport { configureNotificationCenter } from \"./src/lib/notificationCenter\";\n\nconfigureNotificationCenter();\n\nAppRegistry.registerComponent(appName, () =>\n  Config.IS_STORYBOOK === \"true\" ? StorybookUI : App\n);\n"
  },
  {
    "path": "pilaf/ios/Podfile",
    "content": "require_relative '../node_modules/react-native/scripts/react_native_pods'\nrequire_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'\n\nplatform :ios, '11.0'\n\ntarget 'rice' do\n  config = use_native_modules!\n\n  use_react_native!(:path => config[\"reactNativePath\"])\n\n  target 'riceTests' do\n    inherit! :complete\n    # Pods for testing\n  end\n\n  # Enables Flipper.\n  #\n  # Note that if you have use_frameworks! enabled, Flipper will not work and\n  # you should disable these next few lines.\n  use_flipper!({ 'Flipper' => '0.80.0' })\n  post_install do |installer|\n    flipper_post_install(installer)\n  end\nend\n\ntarget 'rice-tvOS' do\n  # Pods for rice-tvOS\n\n  target 'rice-tvOSTests' do\n    inherit! :search_paths\n    # Pods for testing\n  end\nend\n"
  },
  {
    "path": "pilaf/ios/rice/AppDelegate.h",
    "content": "#import <React/RCTBridgeDelegate.h>\n#import <UIKit/UIKit.h>\n#import <UserNotifications/UNUserNotificationCenter.h>\n\n@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>\n\n@property (nonatomic, strong) UIWindow *window;\n\n@end\n"
  },
  {
    "path": "pilaf/ios/rice/AppDelegate.m",
    "content": "#import \"AppDelegate.h\"\n\n#import <React/RCTBridge.h>\n#import <React/RCTBundleURLProvider.h>\n#import <React/RCTRootView.h>\n\n#ifdef FB_SONARKIT_ENABLED\n#import <FlipperKit/FlipperClient.h>\n#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>\n#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>\n#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>\n#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>\n#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>\n\n#import \"RNSplashScreen.h\"\n#import <React/RCTLinkingManager.h>\n\n#import <UserNotifications/UserNotifications.h>\n#import <RNCPushNotificationIOS.h>\n\nstatic void InitializeFlipper(UIApplication *application) {\n  FlipperClient *client = [FlipperClient sharedClient];\n  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];\n  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];\n  [client addPlugin:[FlipperKitReactPlugin new]];\n  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n  [client start];\n}\n#endif\n\n@implementation AppDelegate\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions\n{\n#ifdef FB_SONARKIT_ENABLED\n  InitializeFlipper(application);\n#endif\n\n  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];\n  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge\n                                                   moduleName:@\"rice\"\n                                            initialProperties:nil];\n\n  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];\n\n  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];\n  UIViewController *rootViewController = [UIViewController new];\n  rootViewController.view = rootView;\n  self.window.rootViewController = rootViewController;\n  [self.window makeKeyAndVisible];\n  [RNSplashScreen show];\n  \n  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];\n    center.delegate = self;\n  \n  return YES;\n}\n\n-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler\n{\n  NSDictionary *userInfo = notification.request.content.userInfo;\n    [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo\n                                  fetchCompletionHandler:^void (UIBackgroundFetchResult result){}];\n  completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);\n}\n\n- (BOOL)application:(UIApplication *)application\n   openURL:(NSURL *)url\n   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options\n{\n  return [RCTLinkingManager application:application openURL:url options:options];\n}\n\n- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge\n{\n#if DEBUG\n  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@\"index\" fallbackResource:nil];\n#else\n  return [[NSBundle mainBundle] URLForResource:@\"main\" withExtension:@\"jsbundle\"];\n#endif\n}\n\n- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken\n{\n [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];\n}\n// Required for the notification event. You must call the completion handler after handling the remote notification.\n- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo\nfetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler\n{\n  [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];\n}\n// Required for the registrationError event.\n- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error\n{\n [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];\n}\n// Required for localNotification event\n- (void)userNotificationCenter:(UNUserNotificationCenter *)center\ndidReceiveNotificationResponse:(UNNotificationResponse *)response\n         withCompletionHandler:(void (^)(void))completionHandler\n{\n  [RNCPushNotificationIOS didReceiveNotificationResponse:response];\n}\n\n@end\n"
  },
  {
    "path": "pilaf/ios/rice/Images.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\"images\":[{\"size\":\"60x60\",\"expected-size\":\"180\",\"filename\":\"180.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"3x\"},{\"size\":\"40x40\",\"expected-size\":\"80\",\"filename\":\"80.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"2x\"},{\"size\":\"40x40\",\"expected-size\":\"120\",\"filename\":\"120.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"3x\"},{\"size\":\"60x60\",\"expected-size\":\"120\",\"filename\":\"120.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"2x\"},{\"size\":\"57x57\",\"expected-size\":\"57\",\"filename\":\"57.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"1x\"},{\"size\":\"29x29\",\"expected-size\":\"58\",\"filename\":\"58.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"2x\"},{\"size\":\"29x29\",\"expected-size\":\"29\",\"filename\":\"29.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"1x\"},{\"size\":\"29x29\",\"expected-size\":\"87\",\"filename\":\"87.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"3x\"},{\"size\":\"57x57\",\"expected-size\":\"114\",\"filename\":\"114.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"2x\"},{\"size\":\"20x20\",\"expected-size\":\"40\",\"filename\":\"40.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"2x\"},{\"size\":\"20x20\",\"expected-size\":\"60\",\"filename\":\"60.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"iphone\",\"scale\":\"3x\"},{\"size\":\"1024x1024\",\"filename\":\"1024.png\",\"expected-size\":\"1024\",\"idiom\":\"ios-marketing\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"scale\":\"1x\"},{\"size\":\"40x40\",\"expected-size\":\"80\",\"filename\":\"80.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"},{\"size\":\"72x72\",\"expected-size\":\"72\",\"filename\":\"72.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"1x\"},{\"size\":\"76x76\",\"expected-size\":\"152\",\"filename\":\"152.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"},{\"size\":\"50x50\",\"expected-size\":\"100\",\"filename\":\"100.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"},{\"size\":\"29x29\",\"expected-size\":\"58\",\"filename\":\"58.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"},{\"size\":\"76x76\",\"expected-size\":\"76\",\"filename\":\"76.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"1x\"},{\"size\":\"29x29\",\"expected-size\":\"29\",\"filename\":\"29.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"1x\"},{\"size\":\"50x50\",\"expected-size\":\"50\",\"filename\":\"50.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"1x\"},{\"size\":\"72x72\",\"expected-size\":\"144\",\"filename\":\"144.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"},{\"size\":\"40x40\",\"expected-size\":\"40\",\"filename\":\"40.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"1x\"},{\"size\":\"83.5x83.5\",\"expected-size\":\"167\",\"filename\":\"167.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"},{\"size\":\"20x20\",\"expected-size\":\"20\",\"filename\":\"20.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"1x\"},{\"size\":\"20x20\",\"expected-size\":\"40\",\"filename\":\"40.png\",\"folder\":\"Assets.xcassets/AppIcon.appiconset/\",\"idiom\":\"ipad\",\"scale\":\"2x\"}]}"
  },
  {
    "path": "pilaf/ios/rice/Images.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "pilaf/ios/rice/Images.xcassets/SplashIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"logo.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"logo@x2.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"logo@x3.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "pilaf/ios/rice/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>Dogehouse</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>CFBundleURLName</key>\n\t\t\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>dogehouse</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t\t<key>NSExceptionDomains</key>\n\t\t<dict>\n\t\t\t<key>localhost</key>\n\t\t\t<dict>\n\t\t\t\t<key>NSExceptionAllowsInsecureHTTPLoads</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</dict>\n\t</dict>\n\t<key>NSLocationWhenInUseUsageDescription</key>\n\t<string></string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>I want you to speak with me</string>\n\t<key>UIAppFonts</key>\n\t<array>\n\t\t<string>Inter-Black.ttf</string>\n\t\t<string>Inter-Bold.ttf</string>\n\t\t<string>Inter-ExtraBold.ttf</string>\n\t\t<string>Inter-ExtraLight.ttf</string>\n\t\t<string>Inter-Light.ttf</string>\n\t\t<string>Inter-Medium.ttf</string>\n\t\t<string>Inter-Regular.ttf</string>\n\t\t<string>Inter-SemiBold.ttf</string>\n\t\t<string>Inter-Thin.ttf</string>\n\t\t<string>Ionicons.ttf</string>\n\t</array>\n\t<key>UIBackgroundModes</key>\n\t<array>\n\t\t<string>remote-notification</string>\n\t</array>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UIStatusBarStyle</key>\n\t<string>UIStatusBarStyleLightContent</string>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "pilaf/ios/rice/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"17701\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <device id=\"retina4_7\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"17703\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView clipsSubviews=\"YES\" userInteractionEnabled=\"NO\" contentMode=\"scaleAspectFit\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"251\" image=\"SplashIcon\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"Y1a-qW-nfl\">\n                                <rect key=\"frame\" x=\"37.5\" y=\"183.5\" width=\"300\" height=\"300\"/>\n                            </imageView>\n                        </subviews>\n                        <viewLayoutGuide key=\"safeArea\" id=\"Bcu-3y-fUS\"/>\n                        <color key=\"backgroundColor\" red=\"0.043137254901960784\" green=\"0.054901960784313725\" blue=\"0.066666666666666666\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                        <constraints>\n                            <constraint firstItem=\"Y1a-qW-nfl\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"oWa-gC-AXB\"/>\n                            <constraint firstItem=\"Y1a-qW-nfl\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"uZA-ho-Qap\"/>\n                        </constraints>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"52.173913043478265\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"SplashIcon\" width=\"300\" height=\"300\"/>\n    </resources>\n</document>\n"
  },
  {
    "path": "pilaf/ios/rice/main.m",
    "content": "#import <UIKit/UIKit.h>\n\n#import \"AppDelegate.h\"\n\nint main(int argc, char * argv[]) {\n  @autoreleasepool {\n    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n  }\n}\n\n"
  },
  {
    "path": "pilaf/ios/rice/rice.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>aps-environment</key>\n\t<string>development</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "pilaf/ios/rice-tvOS/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSExceptionDomains</key>\n\t\t<dict>\n\t\t\t<key>localhost</key>\n\t\t\t<dict>\n\t\t\t\t<key>NSExceptionAllowsInsecureHTTPLoads</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</dict>\n\t</dict>\n\t<key>NSLocationWhenInUseUsageDescription</key>\n\t<string></string>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "pilaf/ios/rice-tvOSTests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "pilaf/ios/rice.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t00E356F31AD99517003FC87E /* riceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* riceTests.m */; };\n\t\t13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };\n\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };\n\t\t13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };\n\t\t161DF60EB66935F796D861A3 /* libPods-rice-riceTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A794589B271B88819DF5409 /* libPods-rice-riceTests.a */; };\n\t\t222FB02B693A4291BD33CEB3 /* Inter-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DE9C06D9B0584ADD99AF2F66 /* Inter-Light.ttf */; };\n\t\t2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };\n\t\t2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };\n\t\t2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };\n\t\t2DCD954D1E0B4F2C00145EB5 /* riceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* riceTests.m */; };\n\t\t3ECD123863BAEB4842C56081 /* libPods-rice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D84B5BF07B097D6A7510DED8 /* libPods-rice.a */; };\n\t\t463CBFB1674EE580C4C02373 /* libPods-rice-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31C45461FC579CA444EC2C90 /* libPods-rice-tvOSTests.a */; };\n\t\t5CFF110015504A688DCF6DF3 /* Inter-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4D45C0D805B24D4F9209A630 /* Inter-Regular.ttf */; };\n\t\t7CAEE8E3CFCF41FF80D7956C /* Inter-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 02B220D13DAA4512B9BF5DCD /* Inter-Black.ttf */; };\n\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };\n\t\t8B31AE93ED6C46768155EEEF /* Inter-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D6CC72ACF5C426BBD0DFD56 /* Inter-SemiBold.ttf */; };\n\t\t8F077413260B5B5C0039B724 /* mute.wav in Resources */ = {isa = PBXBuildFile; fileRef = 8F07740F260B5B5B0039B724 /* mute.wav */; };\n\t\t8F077414260B5B5C0039B724 /* unmute.wav in Resources */ = {isa = PBXBuildFile; fileRef = 8F077410260B5B5B0039B724 /* unmute.wav */; };\n\t\t8F077415260B5B5C0039B724 /* room_chat_mention.wav in Resources */ = {isa = PBXBuildFile; fileRef = 8F077411260B5B5B0039B724 /* room_chat_mention.wav */; };\n\t\t8F077416260B5B5C0039B724 /* room_invite.wav in Resources */ = {isa = PBXBuildFile; fileRef = 8F077412260B5B5B0039B724 /* room_invite.wav */; };\n\t\t93E8635A6864436388858CAA /* Inter-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B6D95EED2A6A40479598C44B /* Inter-Medium.ttf */; };\n\t\t9D696787737B6552A015D7DA /* libPods-rice-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 806D79E9CB06BE5EE6127287 /* libPods-rice-tvOS.a */; };\n\t\tBBD680F56BDC49459574630C /* Inter-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EFA5894332D74574BB5F2BAA /* Inter-ExtraBold.ttf */; };\n\t\tC4543B66EF8F477C9AFBD56F /* Inter-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 949E1EE0791A415888479591 /* Inter-Thin.ttf */; };\n\t\tD2BD577DE08B470491C0AA5B /* Inter-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 27EE141760B24FDBACDFD086 /* Inter-ExtraLight.ttf */; };\n\t\tE1558D383F334D53A1C1B602 /* Inter-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 05CC1A400F7046CAA8B36E72 /* Inter-Bold.ttf */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 13B07F861A680F5B00A75B9A;\n\t\t\tremoteInfo = rice;\n\t\t};\n\t\t2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 2D02E47A1E0B4A5D006451C7;\n\t\t\tremoteInfo = \"rice-tvOS\";\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\t008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = \"<group>\"; };\n\t\t00E356EE1AD99517003FC87E /* riceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = riceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t00E356F21AD99517003FC87E /* riceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = riceTests.m; sourceTree = \"<group>\"; };\n\t\t02B220D13DAA4512B9BF5DCD /* Inter-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-Black.ttf\"; path = \"../src/assets/fonts/Inter-Black.ttf\"; sourceTree = \"<group>\"; };\n\t\t05CC1A400F7046CAA8B36E72 /* Inter-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-Bold.ttf\"; path = \"../src/assets/fonts/Inter-Bold.ttf\"; sourceTree = \"<group>\"; };\n\t\t0B31DD1D0A6A3538B60A0525 /* Pods-rice-riceTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice-riceTests.debug.xcconfig\"; path = \"Target Support Files/Pods-rice-riceTests/Pods-rice-riceTests.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t13B07F961A680F5B00A75B9A /* rice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rice.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = rice/AppDelegate.h; sourceTree = \"<group>\"; };\n\t\t13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = rice/AppDelegate.m; sourceTree = \"<group>\"; };\n\t\t13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = rice/Images.xcassets; sourceTree = \"<group>\"; };\n\t\t13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rice/Info.plist; sourceTree = \"<group>\"; };\n\t\t13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = rice/main.m; sourceTree = \"<group>\"; };\n\t\t27EE141760B24FDBACDFD086 /* Inter-ExtraLight.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-ExtraLight.ttf\"; path = \"../src/assets/fonts/Inter-ExtraLight.ttf\"; sourceTree = \"<group>\"; };\n\t\t2D02E47B1E0B4A5D006451C7 /* rice-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = \"rice-tvOS.app\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t2D02E4901E0B4A5D006451C7 /* rice-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = \"rice-tvOSTests.xctest\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t2EAC3734D10AB49FB934C8B9 /* Pods-rice.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice.debug.xcconfig\"; path = \"Target Support Files/Pods-rice/Pods-rice.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t31C45461FC579CA444EC2C90 /* libPods-rice-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-rice-tvOSTests.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t3D6CC72ACF5C426BBD0DFD56 /* Inter-SemiBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-SemiBold.ttf\"; path = \"../src/assets/fonts/Inter-SemiBold.ttf\"; sourceTree = \"<group>\"; };\n\t\t4A794589B271B88819DF5409 /* libPods-rice-riceTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-rice-riceTests.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t4D45C0D805B24D4F9209A630 /* Inter-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-Regular.ttf\"; path = \"../src/assets/fonts/Inter-Regular.ttf\"; sourceTree = \"<group>\"; };\n\t\t6322C88D49FBDE009A4C0E78 /* Pods-rice-riceTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice-riceTests.release.xcconfig\"; path = \"Target Support Files/Pods-rice-riceTests/Pods-rice-riceTests.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t6DE37A5F6A7D4D9E7600B6E8 /* Pods-rice-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice-tvOS.debug.xcconfig\"; path = \"Target Support Files/Pods-rice-tvOS/Pods-rice-tvOS.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t806D79E9CB06BE5EE6127287 /* libPods-rice-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-rice-tvOS.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = rice/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t8F07740F260B5B5B0039B724 /* mute.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = mute.wav; path = rice/mute.wav; sourceTree = \"<group>\"; };\n\t\t8F077410260B5B5B0039B724 /* unmute.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = unmute.wav; path = rice/unmute.wav; sourceTree = \"<group>\"; };\n\t\t8F077411260B5B5B0039B724 /* room_chat_mention.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = room_chat_mention.wav; path = rice/room_chat_mention.wav; sourceTree = \"<group>\"; };\n\t\t8F077412260B5B5B0039B724 /* room_invite.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = room_invite.wav; path = rice/room_invite.wav; sourceTree = \"<group>\"; };\n\t\t8F077A3B261DA7040039B724 /* rice.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = rice.entitlements; path = rice/rice.entitlements; sourceTree = \"<group>\"; };\n\t\t949E1EE0791A415888479591 /* Inter-Thin.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-Thin.ttf\"; path = \"../src/assets/fonts/Inter-Thin.ttf\"; sourceTree = \"<group>\"; };\n\t\tA38C5207B0F5B4C8C6299FBE /* Pods-rice-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice-tvOSTests.debug.xcconfig\"; path = \"Target Support Files/Pods-rice-tvOSTests/Pods-rice-tvOSTests.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tB6D95EED2A6A40479598C44B /* Inter-Medium.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-Medium.ttf\"; path = \"../src/assets/fonts/Inter-Medium.ttf\"; sourceTree = \"<group>\"; };\n\t\tBA75BA238D79F8CC1DFC1D9C /* Pods-rice-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice-tvOSTests.release.xcconfig\"; path = \"Target Support Files/Pods-rice-tvOSTests/Pods-rice-tvOSTests.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tBEF177E6C6BFB1901C7023FE /* Pods-rice.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice.release.xcconfig\"; path = \"Target Support Files/Pods-rice/Pods-rice.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tD84B5BF07B097D6A7510DED8 /* libPods-rice.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-rice.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tD9A6DAC03555005E8F142D9A /* Pods-rice-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-rice-tvOS.release.xcconfig\"; path = \"Target Support Files/Pods-rice-tvOS/Pods-rice-tvOS.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tDE9C06D9B0584ADD99AF2F66 /* Inter-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-Light.ttf\"; path = \"../src/assets/fonts/Inter-Light.ttf\"; sourceTree = \"<group>\"; };\n\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };\n\t\tED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; };\n\t\tEFA5894332D74574BB5F2BAA /* Inter-ExtraBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = \"Inter-ExtraBold.ttf\"; path = \"../src/assets/fonts/Inter-ExtraBold.ttf\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t00E356EB1AD99517003FC87E /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t161DF60EB66935F796D861A3 /* libPods-rice-riceTests.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t3ECD123863BAEB4842C56081 /* libPods-rice.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2D02E4781E0B4A5D006451C7 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t9D696787737B6552A015D7DA /* libPods-rice-tvOS.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2D02E48D1E0B4A5D006451C7 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t463CBFB1674EE580C4C02373 /* libPods-rice-tvOSTests.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t00E356EF1AD99517003FC87E /* riceTests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t00E356F21AD99517003FC87E /* riceTests.m */,\n\t\t\t\t00E356F01AD99517003FC87E /* Supporting Files */,\n\t\t\t);\n\t\t\tpath = riceTests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t00E356F01AD99517003FC87E /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t00E356F11AD99517003FC87E /* Info.plist */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t13B07FAE1A68108700A75B9A /* rice */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t8F077A3B261DA7040039B724 /* rice.entitlements */,\n\t\t\t\t8F07740F260B5B5B0039B724 /* mute.wav */,\n\t\t\t\t8F077411260B5B5B0039B724 /* room_chat_mention.wav */,\n\t\t\t\t8F077412260B5B5B0039B724 /* room_invite.wav */,\n\t\t\t\t8F077410260B5B5B0039B724 /* unmute.wav */,\n\t\t\t\t008F07F21AC5B25A0029DE68 /* main.jsbundle */,\n\t\t\t\t13B07FAF1A68108700A75B9A /* AppDelegate.h */,\n\t\t\t\t13B07FB01A68108700A75B9A /* AppDelegate.m */,\n\t\t\t\t13B07FB51A68108700A75B9A /* Images.xcassets */,\n\t\t\t\t13B07FB61A68108700A75B9A /* Info.plist */,\n\t\t\t\t81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,\n\t\t\t\t13B07FB71A68108700A75B9A /* main.m */,\n\t\t\t);\n\t\t\tname = rice;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */,\n\t\t\t\tED2971642150620600B7C4FE /* JavaScriptCore.framework */,\n\t\t\t\tD84B5BF07B097D6A7510DED8 /* libPods-rice.a */,\n\t\t\t\t4A794589B271B88819DF5409 /* libPods-rice-riceTests.a */,\n\t\t\t\t806D79E9CB06BE5EE6127287 /* libPods-rice-tvOS.a */,\n\t\t\t\t31C45461FC579CA444EC2C90 /* libPods-rice-tvOSTests.a */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t48316C5B49FE022C2B1B9EDA /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2EAC3734D10AB49FB934C8B9 /* Pods-rice.debug.xcconfig */,\n\t\t\t\tBEF177E6C6BFB1901C7023FE /* Pods-rice.release.xcconfig */,\n\t\t\t\t0B31DD1D0A6A3538B60A0525 /* Pods-rice-riceTests.debug.xcconfig */,\n\t\t\t\t6322C88D49FBDE009A4C0E78 /* Pods-rice-riceTests.release.xcconfig */,\n\t\t\t\t6DE37A5F6A7D4D9E7600B6E8 /* Pods-rice-tvOS.debug.xcconfig */,\n\t\t\t\tD9A6DAC03555005E8F142D9A /* Pods-rice-tvOS.release.xcconfig */,\n\t\t\t\tA38C5207B0F5B4C8C6299FBE /* Pods-rice-tvOSTests.debug.xcconfig */,\n\t\t\t\tBA75BA238D79F8CC1DFC1D9C /* Pods-rice-tvOSTests.release.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t79FD313129D4446E8E5109B6 /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t02B220D13DAA4512B9BF5DCD /* Inter-Black.ttf */,\n\t\t\t\t05CC1A400F7046CAA8B36E72 /* Inter-Bold.ttf */,\n\t\t\t\tEFA5894332D74574BB5F2BAA /* Inter-ExtraBold.ttf */,\n\t\t\t\t27EE141760B24FDBACDFD086 /* Inter-ExtraLight.ttf */,\n\t\t\t\tDE9C06D9B0584ADD99AF2F66 /* Inter-Light.ttf */,\n\t\t\t\tB6D95EED2A6A40479598C44B /* Inter-Medium.ttf */,\n\t\t\t\t4D45C0D805B24D4F9209A630 /* Inter-Regular.ttf */,\n\t\t\t\t3D6CC72ACF5C426BBD0DFD56 /* Inter-SemiBold.ttf */,\n\t\t\t\t949E1EE0791A415888479591 /* Inter-Thin.ttf */,\n\t\t\t);\n\t\t\tname = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t832341AE1AAA6A7D00B99B32 /* Libraries */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Libraries;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t83CBB9F61A601CBA00E9B192 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FAE1A68108700A75B9A /* rice */,\n\t\t\t\t832341AE1AAA6A7D00B99B32 /* Libraries */,\n\t\t\t\t00E356EF1AD99517003FC87E /* riceTests */,\n\t\t\t\t83CBBA001A601CBA00E9B192 /* Products */,\n\t\t\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */,\n\t\t\t\t48316C5B49FE022C2B1B9EDA /* Pods */,\n\t\t\t\t79FD313129D4446E8E5109B6 /* Resources */,\n\t\t\t);\n\t\t\tindentWidth = 2;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 2;\n\t\t\tusesTabs = 0;\n\t\t};\n\t\t83CBBA001A601CBA00E9B192 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07F961A680F5B00A75B9A /* rice.app */,\n\t\t\t\t00E356EE1AD99517003FC87E /* riceTests.xctest */,\n\t\t\t\t2D02E47B1E0B4A5D006451C7 /* rice-tvOS.app */,\n\t\t\t\t2D02E4901E0B4A5D006451C7 /* rice-tvOSTests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t00E356ED1AD99517003FC87E /* riceTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget \"riceTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t5CA0ADFCBC132762CE94D7EB /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t00E356EA1AD99517003FC87E /* Sources */,\n\t\t\t\t00E356EB1AD99517003FC87E /* Frameworks */,\n\t\t\t\t00E356EC1AD99517003FC87E /* Resources */,\n\t\t\t\tDDF850442C983F1F1F4A0B27 /* [CP] Embed Pods Frameworks */,\n\t\t\t\t4B3489C862470D459BBDF402 /* [CP] Copy Pods Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t00E356F51AD99517003FC87E /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = riceTests;\n\t\t\tproductName = riceTests;\n\t\t\tproductReference = 00E356EE1AD99517003FC87E /* riceTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t13B07F861A680F5B00A75B9A /* rice */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"rice\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t3CEFB5A12C4E4DD633026C7E /* [CP] Check Pods Manifest.lock */,\n\t\t\t\tFD10A7F022414F080027D42C /* Start Packager */,\n\t\t\t\t13B07F871A680F5B00A75B9A /* Sources */,\n\t\t\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */,\n\t\t\t\t13B07F8E1A680F5B00A75B9A /* Resources */,\n\t\t\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,\n\t\t\t\tF3C40753C5524392BF5E4C66 /* [CP] Embed Pods Frameworks */,\n\t\t\t\t237032B824651EBD7D04CB49 /* [CP] Copy Pods Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = rice;\n\t\t\tproductName = rice;\n\t\t\tproductReference = 13B07F961A680F5B00A75B9A /* rice.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\t2D02E47A1E0B4A5D006451C7 /* rice-tvOS */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget \"rice-tvOS\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t019A00A5ECFD100273A0897F /* [CP] Check Pods Manifest.lock */,\n\t\t\t\tFD10A7F122414F3F0027D42C /* Start Packager */,\n\t\t\t\t2D02E4771E0B4A5D006451C7 /* Sources */,\n\t\t\t\t2D02E4781E0B4A5D006451C7 /* Frameworks */,\n\t\t\t\t2D02E4791E0B4A5D006451C7 /* Resources */,\n\t\t\t\t2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = \"rice-tvOS\";\n\t\t\tproductName = \"rice-tvOS\";\n\t\t\tproductReference = 2D02E47B1E0B4A5D006451C7 /* rice-tvOS.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\t2D02E48F1E0B4A5D006451C7 /* rice-tvOSTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget \"rice-tvOSTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t92634CE141D32192CB419B78 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t2D02E48C1E0B4A5D006451C7 /* Sources */,\n\t\t\t\t2D02E48D1E0B4A5D006451C7 /* Frameworks */,\n\t\t\t\t2D02E48E1E0B4A5D006451C7 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = \"rice-tvOSTests\";\n\t\t\tproductName = \"rice-tvOSTests\";\n\t\t\tproductReference = 2D02E4901E0B4A5D006451C7 /* rice-tvOSTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t83CBB9F71A601CBA00E9B192 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1130;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t00E356ED1AD99517003FC87E = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.2;\n\t\t\t\t\t\tTestTargetID = 13B07F861A680F5B00A75B9A;\n\t\t\t\t\t};\n\t\t\t\t\t13B07F861A680F5B00A75B9A = {\n\t\t\t\t\t\tLastSwiftMigration = 1120;\n\t\t\t\t\t};\n\t\t\t\t\t2D02E47A1E0B4A5D006451C7 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.2.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t\t2D02E48F1E0B4A5D006451C7 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.2.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t\tTestTargetID = 2D02E47A1E0B4A5D006451C7;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"rice\" */;\n\t\t\tcompatibilityVersion = \"Xcode 3.2\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 83CBB9F61A601CBA00E9B192;\n\t\t\tproductRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t13B07F861A680F5B00A75B9A /* rice */,\n\t\t\t\t00E356ED1AD99517003FC87E /* riceTests */,\n\t\t\t\t2D02E47A1E0B4A5D006451C7 /* rice-tvOS */,\n\t\t\t\t2D02E48F1E0B4A5D006451C7 /* rice-tvOSTests */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t00E356EC1AD99517003FC87E /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t13B07F8E1A680F5B00A75B9A /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,\n\t\t\t\t8F077415260B5B5C0039B724 /* room_chat_mention.wav in Resources */,\n\t\t\t\t7CAEE8E3CFCF41FF80D7956C /* Inter-Black.ttf in Resources */,\n\t\t\t\t8F077413260B5B5C0039B724 /* mute.wav in Resources */,\n\t\t\t\tE1558D383F334D53A1C1B602 /* Inter-Bold.ttf in Resources */,\n\t\t\t\tBBD680F56BDC49459574630C /* Inter-ExtraBold.ttf in Resources */,\n\t\t\t\t8F077416260B5B5C0039B724 /* room_invite.wav in Resources */,\n\t\t\t\tD2BD577DE08B470491C0AA5B /* Inter-ExtraLight.ttf in Resources */,\n\t\t\t\t8F077414260B5B5C0039B724 /* unmute.wav in Resources */,\n\t\t\t\t222FB02B693A4291BD33CEB3 /* Inter-Light.ttf in Resources */,\n\t\t\t\t93E8635A6864436388858CAA /* Inter-Medium.ttf in Resources */,\n\t\t\t\t5CFF110015504A688DCF6DF3 /* Inter-Regular.ttf in Resources */,\n\t\t\t\t8B31AE93ED6C46768155EEEF /* Inter-SemiBold.ttf in Resources */,\n\t\t\t\tC4543B66EF8F477C9AFBD56F /* Inter-Thin.ttf in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2D02E4791E0B4A5D006451C7 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2D02E48E1E0B4A5D006451C7 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Bundle React Native code and images\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"export NODE_BINARY=node\\n../node_modules/react-native/scripts/react-native-xcode.sh\";\n\t\t};\n\t\t019A00A5ECFD100273A0897F /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-rice-tvOS-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t237032B824651EBD7D04CB49 /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-rice/Pods-rice-resources.sh\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-rice/Pods-rice-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Bundle React Native Code And Images\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"export NODE_BINARY=node\\n../node_modules/react-native/scripts/react-native-xcode.sh\";\n\t\t};\n\t\t3CEFB5A12C4E4DD633026C7E /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-rice-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t4B3489C862470D459BBDF402 /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-rice-riceTests/Pods-rice-riceTests-resources.sh\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf\",\n\t\t\t\t\"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-rice-riceTests/Pods-rice-riceTests-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t5CA0ADFCBC132762CE94D7EB /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-rice-riceTests-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t92634CE141D32192CB419B78 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-rice-tvOSTests-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tDDF850442C983F1F1F4A0B27 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-rice-riceTests/Pods-rice-riceTests-frameworks.sh\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/WebRTC/WebRTC.framework/WebRTC\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-rice-riceTests/Pods-rice-riceTests-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tF3C40753C5524392BF5E4C66 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-rice/Pods-rice-frameworks.sh\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/WebRTC/WebRTC.framework/WebRTC\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-rice/Pods-rice-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tFD10A7F022414F080027D42C /* Start Packager */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Start Packager\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"export RCT_METRO_PORT=\\\"${RCT_METRO_PORT:=8081}\\\"\\necho \\\"export RCT_METRO_PORT=${RCT_METRO_PORT}\\\" > \\\"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\\\"\\nif [ -z \\\"${RCT_NO_LAUNCH_PACKAGER+xxx}\\\" ] ; then\\n  if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\\n    if ! curl -s \\\"http://localhost:${RCT_METRO_PORT}/status\\\" | grep -q \\\"packager-status:running\\\" ; then\\n      echo \\\"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\\\"\\n      exit 2\\n    fi\\n  else\\n    open \\\"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\\\" || echo \\\"Can't start packager automatically\\\"\\n  fi\\nfi\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tFD10A7F122414F3F0027D42C /* Start Packager */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Start Packager\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"export RCT_METRO_PORT=\\\"${RCT_METRO_PORT:=8081}\\\"\\necho \\\"export RCT_METRO_PORT=${RCT_METRO_PORT}\\\" > \\\"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\\\"\\nif [ -z \\\"${RCT_NO_LAUNCH_PACKAGER+xxx}\\\" ] ; then\\n  if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\\n    if ! curl -s \\\"http://localhost:${RCT_METRO_PORT}/status\\\" | grep -q \\\"packager-status:running\\\" ; then\\n      echo \\\"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\\\"\\n      exit 2\\n    fi\\n  else\\n    open \\\"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\\\" || echo \\\"Can't start packager automatically\\\"\\n  fi\\nfi\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t00E356EA1AD99517003FC87E /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t00E356F31AD99517003FC87E /* riceTests.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t13B07F871A680F5B00A75B9A /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,\n\t\t\t\t13B07FC11A68108700A75B9A /* main.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2D02E4771E0B4A5D006451C7 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */,\n\t\t\t\t2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2D02E48C1E0B4A5D006451C7 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2DCD954D1E0B4F2C00145EB5 /* riceTests.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t00E356F51AD99517003FC87E /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 13B07F861A680F5B00A75B9A /* rice */;\n\t\t\ttargetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;\n\t\t};\n\t\t2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 2D02E47A1E0B4A5D006451C7 /* rice-tvOS */;\n\t\t\ttargetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\t00E356F61AD99517003FC87E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 0B31DD1D0A6A3538B60A0525 /* Pods-rice-riceTests.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = riceTests/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 10.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/rice.app/rice\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t00E356F71AD99517003FC87E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 6322C88D49FBDE009A4C0E78 /* Pods-rice-riceTests.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tINFOPLIST_FILE = riceTests/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 10.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/rice.app/rice\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t13B07F941A680F5B00A75B9A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 2EAC3734D10AB49FB934C8B9 /* Pods-rice.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = rice/rice.entitlements;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = rice/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.dogehouse;\n\t\t\t\tPRODUCT_NAME = rice;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t13B07F951A680F5B00A75B9A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = BEF177E6C6BFB1901C7023FE /* Pods-rice.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = rice/rice.entitlements;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tINFOPLIST_FILE = rice/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.dogehouse;\n\t\t\t\tPRODUCT_NAME = rice;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t2D02E4971E0B4A5E006451C7 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 6DE37A5F6A7D4D9E7600B6E8 /* Pods-rice-tvOS.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"App Icon & Top Shelf Image\";\n\t\t\t\tASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tINFOPLIST_FILE = \"rice-tvOS/Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"org.reactjs.native.example.rice-tvOS\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = appletvos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = 3;\n\t\t\t\tTVOS_DEPLOYMENT_TARGET = 10.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t2D02E4981E0B4A5E006451C7 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = D9A6DAC03555005E8F142D9A /* Pods-rice-tvOS.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = \"App Icon & Top Shelf Image\";\n\t\t\t\tASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tINFOPLIST_FILE = \"rice-tvOS/Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"org.reactjs.native.example.rice-tvOS\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = appletvos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = 3;\n\t\t\t\tTVOS_DEPLOYMENT_TARGET = 10.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t2D02E4991E0B4A5E006451C7 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = A38C5207B0F5B4C8C6299FBE /* Pods-rice-tvOSTests.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tINFOPLIST_FILE = \"rice-tvOSTests/Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"org.reactjs.native.example.rice-tvOSTests\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = appletvos;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/rice-tvOS.app/rice-tvOS\";\n\t\t\t\tTVOS_DEPLOYMENT_TARGET = 10.1;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t2D02E49A1E0B4A5E006451C7 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = BA75BA238D79F8CC1DFC1D9C /* Pods-rice-tvOSTests.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tINFOPLIST_FILE = \"rice-tvOSTests/Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/Frameworks @loader_path/Frameworks\";\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"org.reactjs.native.example.rice-tvOSTests\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = appletvos;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/rice-tvOS.app/rice-tvOS\";\n\t\t\t\tTVOS_DEPLOYMENT_TARGET = 10.1;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t83CBBA201A601CBA00E9B192 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 10.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"/usr/lib/swift $(inherited)\";\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t83CBBA211A601CBA00E9B192 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 10.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"/usr/lib/swift $(inherited)\";\n\t\t\t\tLIBRARY_SEARCH_PATHS = (\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\\\"\",\n\t\t\t\t\t\"\\\"$(inherited)\\\"\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget \"riceTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t00E356F61AD99517003FC87E /* Debug */,\n\t\t\t\t00E356F71AD99517003FC87E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"rice\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t13B07F941A680F5B00A75B9A /* Debug */,\n\t\t\t\t13B07F951A680F5B00A75B9A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget \"rice-tvOS\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t2D02E4971E0B4A5E006451C7 /* Debug */,\n\t\t\t\t2D02E4981E0B4A5E006451C7 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget \"rice-tvOSTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t2D02E4991E0B4A5E006451C7 /* Debug */,\n\t\t\t\t2D02E49A1E0B4A5E006451C7 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"rice\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t83CBBA201A601CBA00E9B192 /* Debug */,\n\t\t\t\t83CBBA211A601CBA00E9B192 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;\n}\n"
  },
  {
    "path": "pilaf/ios/rice.xcodeproj/xcshareddata/xcschemes/rice-tvOS.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1130\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"2D02E47A1E0B4A5D006451C7\"\n               BuildableName = \"rice-tvOS.app\"\n               BlueprintName = \"rice-tvOS\"\n               ReferencedContainer = \"container:rice.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"2D02E48F1E0B4A5D006451C7\"\n               BuildableName = \"rice-tvOSTests.xctest\"\n               BlueprintName = \"rice-tvOSTests\"\n               ReferencedContainer = \"container:rice.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"2D02E47A1E0B4A5D006451C7\"\n            BuildableName = \"rice-tvOS.app\"\n            BlueprintName = \"rice-tvOS\"\n            ReferencedContainer = \"container:rice.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"2D02E47A1E0B4A5D006451C7\"\n            BuildableName = \"rice-tvOS.app\"\n            BlueprintName = \"rice-tvOS\"\n            ReferencedContainer = \"container:rice.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "pilaf/ios/rice.xcodeproj/xcshareddata/xcschemes/rice.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1130\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n               BuildableName = \"rice.app\"\n               BlueprintName = \"rice\"\n               ReferencedContainer = \"container:rice.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"00E356ED1AD99517003FC87E\"\n               BuildableName = \"riceTests.xctest\"\n               BlueprintName = \"riceTests\"\n               ReferencedContainer = \"container:rice.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"rice.app\"\n            BlueprintName = \"rice\"\n            ReferencedContainer = \"container:rice.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"rice.app\"\n            BlueprintName = \"rice\"\n            ReferencedContainer = \"container:rice.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "pilaf/ios/rice.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:rice.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "pilaf/ios/rice.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "pilaf/ios/riceTests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "pilaf/ios/riceTests/riceTests.m",
    "content": "#import <UIKit/UIKit.h>\n#import <XCTest/XCTest.h>\n\n#import <React/RCTLog.h>\n#import <React/RCTRootView.h>\n\n#define TIMEOUT_SECONDS 600\n#define TEXT_TO_LOOK_FOR @\"Welcome to React\"\n\n@interface riceTests : XCTestCase\n\n@end\n\n@implementation riceTests\n\n- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test\n{\n  if (test(view)) {\n    return YES;\n  }\n  for (UIView *subview in [view subviews]) {\n    if ([self findSubviewInView:subview matching:test]) {\n      return YES;\n    }\n  }\n  return NO;\n}\n\n- (void)testRendersWelcomeScreen\n{\n  UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];\n  NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];\n  BOOL foundElement = NO;\n\n  __block NSString *redboxError = nil;\n#ifdef DEBUG\n  RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {\n    if (level >= RCTLogLevelError) {\n      redboxError = message;\n    }\n  });\n#endif\n\n  while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {\n    [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];\n    [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];\n\n    foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {\n      if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {\n        return YES;\n      }\n      return NO;\n    }];\n  }\n\n#ifdef DEBUG\n  RCTSetLogFunction(RCTDefaultLogFunction);\n#endif\n\n  XCTAssertNil(redboxError, @\"RedBox error: %@\", redboxError);\n  XCTAssertTrue(foundElement, @\"Couldn't find element with text '%@' in %d seconds\", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);\n}\n\n\n@end\n"
  },
  {
    "path": "pilaf/metro.config.js",
    "content": "/**\n * Metro configuration for React Native\n * https://github.com/facebook/react-native\n *\n * @format\n */\nconst path = require(\"path\");\n\nconst watchFolders = [\n  path.resolve(__dirname, \"..\", \"node_modules\"),\n  path.resolve(path.join(__dirname, \"/../kebab/\")),\n];\n\nmodule.exports = {\n  transformer: {\n    getTransformOptions: async () => ({\n      transform: {\n        experimentalImportSupport: false,\n        inlineRequires: false,\n      },\n    }),\n  },\n  watchFolders,\n};\n"
  },
  {
    "path": "pilaf/package.json",
    "content": "{\n  \"name\": \"rice\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"scripts\": {\n    \"android\": \"react-native run-android\",\n    \"ios\": \"react-native run-ios\",\n    \"start\": \"react-native start\",\n    \"test\": \"jest\",\n    \"lint\": \"eslint .\",\n    \"storybook\": \"start-storybook -p 7007\",\n    \"build-storybook\": \"build-storybook\",\n    \"prettier\": \"prettier --write ./src\"\n  },\n  \"dependencies\": {\n    \"@react-native-async-storage/async-storage\": \"^1.14.1\",\n    \"@react-native-community/hooks\": \"^2.6.0\",\n    \"@react-native-community/masked-view\": \"^0.1.10\",\n    \"@react-native-community/push-notification-ios\": \"^1.8.0\",\n    \"@react-native-segmented-control/segmented-control\": \"^2.3.0\",\n    \"@react-navigation/bottom-tabs\": \"^5.11.8\",\n    \"@react-navigation/native\": \"^5.9.3\",\n    \"@react-navigation/stack\": \"^5.14.3\",\n    \"date-fns\": \"^2.19.0\",\n    \"emoji-mart\": \"^3.0.1\",\n    \"formik\": \"^2.2.6\",\n    \"hark\": \"^1.2.3\",\n    \"normalize-url\": \"^5.3.0\",\n    \"react\": \"17.0.1\",\n    \"react-native\": \"0.64.0\",\n    \"react-native-config\": \"^1.4.2\",\n    \"react-native-gesture-handler\": \"^1.10.3\",\n    \"react-native-get-random-values\": \"^1.6.0\",\n    \"react-native-inappbrowser-reborn\": \"^3.5.1\",\n    \"react-native-incall-manager\": \"^3.3.0\",\n    \"react-native-modal\": \"^11.7.0\",\n    \"react-native-push-notification\": \"^7.2.3\",\n    \"react-native-reanimated\": \"^2.0.0\",\n    \"react-native-safe-area-context\": \"^3.2.0\",\n    \"react-native-screens\": \"^2.18.1\",\n    \"react-native-slider\": \"^0.11.0\",\n    \"react-native-sound\": \"^0.11.0\",\n    \"react-native-splash-screen\": \"^3.2.0\",\n    \"react-native-toast-message\": \"^1.4.9\",\n    \"react-native-vector-icons\": \"^8.1.0\",\n    \"react-native-webrtc\": \"^1.89.1\",\n    \"react-query\": \"^3.13.0\",\n    \"reanimated-bottom-sheet\": \"^1.0.0-alpha.22\",\n    \"uuid\": \"^8.3.2\",\n    \"zustand\": \"^3.3.3\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.12.9\",\n    \"@babel/runtime\": \"^7.12.5\",\n    \"@react-native-community/eslint-config\": \"^2.0.0\",\n    \"@storybook/addon-actions\": \"^5.3\",\n    \"@storybook/addon-essentials\": \"^6.1.21\",\n    \"@storybook/addon-knobs\": \"^5.3\",\n    \"@storybook/addon-links\": \"^5.3\",\n    \"@storybook/addon-ondevice-actions\": \"^5.3.23\",\n    \"@storybook/addon-ondevice-knobs\": \"^5.3.25\",\n    \"@storybook/addons\": \"^6.1.21\",\n    \"@storybook/react-native\": \"^5.3.25\",\n    \"@storybook/react-native-server\": \"^5.3.23\",\n    \"@storybook/theming\": \"^6.1.21\",\n    \"@types/react-native\": \"^0.63.52\",\n    \"@types/react-native-push-notification\": \"^7.2.0\",\n    \"babel-jest\": \"^26.6.3\",\n    \"babel-loader\": \"^8.2.2\",\n    \"eslint\": \"7.14.0\",\n    \"jest\": \"^26.6.3\",\n    \"metro-react-native-babel-preset\": \"^0.64.0\",\n    \"prettier\": \"2.2.1\",\n    \"react-dom\": \"17.0.1\",\n    \"react-test-renderer\": \"17.0.1\",\n    \"typescript\": \"^4.2.3\"\n  },\n  \"jest\": {\n    \"preset\": \"react-native\"\n  }\n}"
  },
  {
    "path": "pilaf/react-native.config.js",
    "content": "module.exports = {\n  assets: [\"./src/assets/fonts\"],\n};\n"
  },
  {
    "path": "pilaf/src/assets/images/logo.svg~Add font + constant for dogehouse theme colors",
    "content": "<svg\n      width=\"40\"\n      height=\"40\"\n      viewBox=\"0 0 40 40\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M10.0006 0H29.3993C34.9418 0 39.3999 4.52599 39.3999 10.1529V29.8471C39.3999 35.474 34.9418 40 29.3993 40H10.0006C4.45809 40 0 35.474 0 29.8471V10.1529C0 4.52599 4.45809 0 10.0006 0Z\"\n        fill=\"#EFE7DC\"\n      />\n      <path\n        d=\"M5.30238 22.1408C5.18189 21.2846 5.66385 20.4283 6.38678 20.0613C7.95314 19.0827 9.88096 18.9604 11.5678 19.6944C12.1703 20.0613 12.7727 20.306 13.3751 20.7953C13.9776 21.1622 14.2186 22.0185 13.8571 22.7525C13.3751 23.6087 12.8932 24.3427 12.1703 25.0766C10.8449 26.7892 8.4351 27.0338 6.62776 25.6882C6.50727 25.5659 6.26629 25.4436 6.14581 25.1989C5.54336 24.3427 5.18189 23.2418 5.30238 22.1408Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M25.6633 17.6147C24.9404 17.4924 24.3379 17.4924 23.7355 17.2477C23.133 17.0031 22.8921 16.3915 23.0125 15.6575C23.374 13.945 24.8199 12.7217 26.5067 12.3548C28.3141 11.9878 30.1214 12.9664 30.9648 14.6789C31.0853 14.9236 30.8443 15.4129 30.8443 15.7798H30.6033V14.8012L29.3985 16.2691C28.6755 17.0031 27.7116 17.3701 26.6272 17.2477C26.1453 17.2477 26.0248 16.8808 26.0248 16.3915C26.0248 15.7798 26.0248 15.2905 26.1453 14.6789C26.2657 14.1896 26.5067 13.578 26.7477 13.0887C26.0248 12.7217 25.0609 13.3334 24.3379 14.5566C23.856 15.5352 24.2174 16.3915 25.6633 17.6147Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M22.6523 31.315C20.6039 31.9266 18.5556 33.0275 16.2663 32.5383C15.9049 32.4159 15.5434 32.2936 15.1819 32.1713C13.4951 31.4373 11.6878 30.948 9.88043 30.8257C8.43456 30.5811 7.10919 30.0918 5.9043 29.3578C6.14527 29.2355 6.38625 29.2355 6.62723 29.2355C7.83212 28.9909 9.1575 29.2355 10.3624 29.7248C11.8083 30.4587 13.2541 30.8257 14.7 31.682C15.6639 32.049 16.7483 32.1713 17.7122 31.8043C18.7966 31.5597 19.7605 31.315 20.8449 31.0704C21.4474 31.0704 22.0498 31.0704 22.6523 31.0704V31.315Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M13.2528 10.8868C12.8914 9.78589 11.566 9.17427 10.4816 9.66356C10.3611 9.66356 10.3611 9.66356 10.2406 9.78589C9.27671 10.1529 8.79476 11.0091 8.67427 11.9877C8.67427 12.11 8.67427 12.3547 8.55378 12.477C8.3128 13.5779 8.67427 13.9449 9.75867 14.0672C10.1201 14.0672 10.4816 14.0672 10.8431 14.0672C11.9275 14.0672 12.8914 13.3333 13.2528 12.2324C13.3733 11.8654 13.3733 11.3761 13.2528 10.8868ZM10.3611 13.8226C9.63818 13.7003 9.15622 13.0886 9.15622 12.477C9.27671 11.2538 10.1201 10.2752 11.325 10.1529C9.3972 11.0091 9.63818 12.3547 10.3611 13.8226Z\"\n        fill=\"black\"\n      />\n    </svg>"
  },
  {
    "path": "pilaf/src/assets/images/logo.svg~refs/remotes/ben/staging",
    "content": "<svg\n      width=\"40\"\n      height=\"40\"\n      viewBox=\"0 0 40 40\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M10.0006 0H29.3993C34.9418 0 39.3999 4.52599 39.3999 10.1529V29.8471C39.3999 35.474 34.9418 40 29.3993 40H10.0006C4.45809 40 0 35.474 0 29.8471V10.1529C0 4.52599 4.45809 0 10.0006 0Z\"\n        fill=\"#EFE7DC\"\n      />\n      <path\n        d=\"M5.30238 22.1408C5.18189 21.2846 5.66385 20.4283 6.38678 20.0613C7.95314 19.0827 9.88096 18.9604 11.5678 19.6944C12.1703 20.0613 12.7727 20.306 13.3751 20.7953C13.9776 21.1622 14.2186 22.0185 13.8571 22.7525C13.3751 23.6087 12.8932 24.3427 12.1703 25.0766C10.8449 26.7892 8.4351 27.0338 6.62776 25.6882C6.50727 25.5659 6.26629 25.4436 6.14581 25.1989C5.54336 24.3427 5.18189 23.2418 5.30238 22.1408Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M25.6633 17.6147C24.9404 17.4924 24.3379 17.4924 23.7355 17.2477C23.133 17.0031 22.8921 16.3915 23.0125 15.6575C23.374 13.945 24.8199 12.7217 26.5067 12.3548C28.3141 11.9878 30.1214 12.9664 30.9648 14.6789C31.0853 14.9236 30.8443 15.4129 30.8443 15.7798H30.6033V14.8012L29.3985 16.2691C28.6755 17.0031 27.7116 17.3701 26.6272 17.2477C26.1453 17.2477 26.0248 16.8808 26.0248 16.3915C26.0248 15.7798 26.0248 15.2905 26.1453 14.6789C26.2657 14.1896 26.5067 13.578 26.7477 13.0887C26.0248 12.7217 25.0609 13.3334 24.3379 14.5566C23.856 15.5352 24.2174 16.3915 25.6633 17.6147Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M22.6523 31.315C20.6039 31.9266 18.5556 33.0275 16.2663 32.5383C15.9049 32.4159 15.5434 32.2936 15.1819 32.1713C13.4951 31.4373 11.6878 30.948 9.88043 30.8257C8.43456 30.5811 7.10919 30.0918 5.9043 29.3578C6.14527 29.2355 6.38625 29.2355 6.62723 29.2355C7.83212 28.9909 9.1575 29.2355 10.3624 29.7248C11.8083 30.4587 13.2541 30.8257 14.7 31.682C15.6639 32.049 16.7483 32.1713 17.7122 31.8043C18.7966 31.5597 19.7605 31.315 20.8449 31.0704C21.4474 31.0704 22.0498 31.0704 22.6523 31.0704V31.315Z\"\n        fill=\"black\"\n      />\n      <path\n        d=\"M13.2528 10.8868C12.8914 9.78589 11.566 9.17427 10.4816 9.66356C10.3611 9.66356 10.3611 9.66356 10.2406 9.78589C9.27671 10.1529 8.79476 11.0091 8.67427 11.9877C8.67427 12.11 8.67427 12.3547 8.55378 12.477C8.3128 13.5779 8.67427 13.9449 9.75867 14.0672C10.1201 14.0672 10.4816 14.0672 10.8431 14.0672C11.9275 14.0672 12.8914 13.3333 13.2528 12.2324C13.3733 11.8654 13.3733 11.3761 13.2528 10.8868ZM10.3611 13.8226C9.63818 13.7003 9.15622 13.0886 9.15622 12.477C9.27671 11.2538 10.1201 10.2752 11.325 10.1529C9.3972 11.0091 9.63818 12.3547 10.3611 13.8226Z\"\n        fill=\"black\"\n      />\n    </svg>"
  },
  {
    "path": "pilaf/src/components/BaseOverlay.tsx",
    "content": "import React, { ReactNode } from \"react\";\nimport {\n  ScrollView,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { colors, fontFamily, fontSize, radius } from \"../constants/dogeStyle\";\n\ninterface BaseOverlayProps {\n  style?: ViewStyle;\n  title?: string;\n  actionButton?: string;\n  onActionButtonClicked?: () => void;\n  children: ReactNode;\n}\n\nexport const BaseOverlay: React.FC<BaseOverlayProps> = ({\n  children,\n  title,\n  style,\n  actionButton,\n  onActionButtonClicked,\n}) => {\n  return (\n    <View style={[style, styles.container]}>\n      {title && (\n        <View style={styles.header}>\n          <Text style={styles.title}>{title}</Text>\n        </View>\n      )}\n      <ScrollView>{children}</ScrollView>\n      {actionButton && (\n        <TouchableOpacity style={styles.button} onPress={onActionButtonClicked}>\n          <Text style={styles.buttonTitle}>{actionButton}</Text>\n        </TouchableOpacity>\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: colors.primary800,\n    borderRadius: radius.m,\n  },\n  header: {\n    borderBottomWidth: 1,\n    borderColor: colors.primary600,\n    padding: 16,\n  },\n  title: {\n    color: colors.text,\n    fontFamily: fontFamily.regular,\n    fontSize: fontSize.h4,\n    fontWeight: \"700\",\n  },\n  button: {\n    backgroundColor: colors.primary700,\n    padding: 16,\n    borderBottomLeftRadius: radius.m,\n    borderBottomRightRadius: radius.m,\n  },\n  buttonTitle: {\n    fontSize: fontSize.paragraph,\n    fontFamily: fontFamily.bold,\n    color: colors.text,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/BubbleText.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View, ViewStyle } from \"react-native\";\nimport { colors } from \"../constants/dogeStyle\";\n\nexport interface BubbleTextProps {\n  style?: ViewStyle;\n  live?: boolean;\n  children: React.ReactNode;\n}\n\nexport const BubbleText: React.FC<BubbleTextProps> = ({\n  live,\n  children,\n  style,\n}) => {\n  return (\n    <View style={[styles.container, style]}>\n      <View\n        style={[\n          styles.liveDot,\n          live\n            ? { backgroundColor: colors.accent }\n            : { backgroundColor: colors.primary300 },\n        ]}\n      />\n      {children}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n  liveDot: {\n    height: 8,\n    width: 8,\n    borderRadius: 4,\n    marginRight: 4,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/ErrorToast.tsx",
    "content": ""
  },
  {
    "path": "pilaf/src/components/FeaturedRoomCard.tsx",
    "content": "import { differenceInMilliseconds, format, isPast, isToday } from \"date-fns\";\nimport React, { useEffect, useState } from \"react\";\nimport {\n  ImageBackground,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  h4,\n  paragraph,\n  paragraphBold,\n  radius,\n} from \"../constants/dogeStyle\";\nimport { MultipleUserAvatar } from \"./avatars/MultipleUserAvatar\";\nimport { BubbleText } from \"./BubbleText\";\n\nfunction formatNumber(num: number): string {\n  return Math.abs(num) > 999\n    ? `${Math.sign(num) * Number((Math.abs(num) / 1000).toFixed(1))}K`\n    : `${Math.sign(num) * Math.abs(num)}`;\n}\n\nfunction useScheduleRerender(scheduledFor?: Date) {\n  // same logic stolen from kofta, rerenders\n  // at the scheduleFor date\n  const [, rerender] = useState(0);\n\n  useEffect(() => {\n    if (!scheduledFor) {\n      return;\n    }\n\n    let done = false;\n    const id = setTimeout(() => {\n      done = true;\n      rerender((x) => x + 1);\n    }, differenceInMilliseconds(scheduledFor, new Date()) + 1000);\n\n    return () => {\n      if (!done) {\n        clearTimeout(id);\n      }\n    };\n  }, [scheduledFor]);\n}\n\nexport type FeaturedRoomCardProps = {\n  style?: ViewStyle;\n  title: string;\n  avatarSrcs: ImageSourcePropType[];\n  subtitle: string;\n  scheduledFor?: Date;\n  listeners: number;\n  tags: React.ReactNode[];\n  onPress?: () => void;\n};\n\nexport const FeaturedRoomCard: React.FC<FeaturedRoomCardProps> = ({\n  style,\n  title,\n  avatarSrcs,\n  subtitle,\n  scheduledFor,\n  listeners,\n  tags,\n  onPress,\n}) => {\n  useScheduleRerender(scheduledFor);\n\n  let scheduledForLabel = \"\";\n\n  if (scheduledFor) {\n    if (isToday(scheduledFor)) {\n      scheduledForLabel = format(scheduledFor, \"K:mm a\");\n    } else {\n      scheduledForLabel = format(scheduledFor, \"LLL d\");\n    }\n  }\n\n  const roomLive = !scheduledFor || isPast(scheduledFor);\n\n  return (\n    <TouchableOpacity style={[styles.container, style]} onPress={onPress}>\n      <ImageBackground\n        style={styles.backgroundImage}\n        source={require(\"../assets/images/featured-card-bg.png\")}\n      >\n        <View style={styles.topContainer}>\n          <View style={styles.topLeftContainer}>\n            <Text style={{ ...h4 }} numberOfLines={2}>\n              {title}\n            </Text>\n            <View style={styles.subtitleContainer}>\n              {avatarSrcs.length > 0 && (\n                <MultipleUserAvatar\n                  srcArray={avatarSrcs}\n                  size={\"md\"}\n                  translationRatio={1.5}\n                />\n              )}\n              <View>\n                <Text style={styles.subtitleLabel}>Hosted by</Text>\n                <Text style={styles.subtitle}>{subtitle}</Text>\n              </View>\n            </View>\n          </View>\n          <View style={styles.topRightContainer}>\n            <BubbleText style={{ alignSelf: \"flex-start\" }} live={roomLive}>\n              <Text style={styles.bubbleLabel}>\n                {roomLive ? formatNumber(listeners) : scheduledForLabel}\n              </Text>\n            </BubbleText>\n          </View>\n        </View>\n        {tags && tags.length > 0 && (\n          <View style={styles.tagsContainer}>{tags.map((tag) => tag)}</View>\n        )}\n      </ImageBackground>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: colors.primary800,\n    borderColor: colors.primary300,\n    borderRadius: radius.m,\n    borderWidth: 1,\n    overflow: \"hidden\", // prevents image from cutting off bottom border\n    shadowColor: colors.primary100,\n    shadowOffset: { width: 0, height: 0 },\n    shadowOpacity: 0.1,\n    shadowRadius: 20,\n  },\n  backgroundImage: {\n    flex: 1,\n    resizeMode: \"cover\",\n    justifyContent: \"center\",\n    padding: 15,\n  },\n  topContainer: {\n    flexDirection: \"row\",\n  },\n  topLeftContainer: {\n    flex: 1,\n  },\n  topRightContainer: {},\n  headingContainer: {\n    flexDirection: \"row\",\n  },\n  subtitleContainer: {\n    flexDirection: \"row\",\n    marginTop: 20,\n  },\n  subtitleLabel: {\n    ...paragraph,\n    color: colors.primary300,\n    flex: 1,\n  },\n  subtitle: {\n    ...paragraph,\n    color: colors.primary100,\n    flex: 1,\n  },\n  tagsContainer: {\n    flexDirection: \"row\",\n    marginTop: 40,\n    borderRadius: radius.s,\n    overflow: \"hidden\",\n  },\n  bubbleLabel: {\n    ...paragraphBold,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/MessageElement.tsx",
    "content": "import { formatDistance, fromUnixTime } from \"date-fns\";\nimport React, { useState } from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport { colors, paragraphBold, small } from \"../constants/dogeStyle\";\nimport { SingleUserAvatar } from \"./avatars/SingleUserAvatar\";\n\nexport type MessageElementProps = ViewProps & {\n  user: {\n    username: string;\n    avatar: ImageSourcePropType;\n    isOnline: boolean;\n  };\n  msg: {\n    ts: number;\n    text: string;\n  };\n};\n\ninterface MessageDateProps {\n  ts: number;\n  style?: TextStyle;\n}\n\nconst MessageDate: React.FC<MessageDateProps> = ({ ts, style }) => (\n  <Text style={style}>{formatDistance(fromUnixTime(ts), new Date())} ago</Text>\n);\n\nexport const MessageElement: React.FC<MessageElementProps> = ({\n  style,\n  user,\n  msg,\n}) => {\n  const [expanded, setExpanded] = useState(false);\n  return (\n    <TouchableOpacity\n      onPress={() => setExpanded(!expanded)}\n      activeOpacity={0.8}\n    >\n      <View style={[styles.container, style]}>\n        <SingleUserAvatar\n          size={\"sm\"}\n          isOnline={user.isOnline}\n          src={user.avatar}\n          style={{ marginBottom: 20 }}\n        />\n        <View style={styles.middleContainer}>\n          <Text style={styles.textUserName}>{user.username}</Text>\n          <Text style={styles.textMessage} numberOfLines={1}>\n            {msg.text}\n          </Text>\n        </View>\n        <View style={styles.dateContainer}>\n          <MessageDate style={styles.textDate} ts={msg.ts} />\n        </View>\n      </View>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n  middleContainer: {\n    flex: 1,\n    borderBottomWidth: 1,\n    borderBottomColor: colors.primary700,\n    paddingBottom: 20,\n    paddingLeft: 0,\n    marginLeft: 11,\n    justifyContent: \"space-between\",\n    overflow: \"hidden\",\n  },\n  dateContainer: {\n    height: \"100%\",\n    flexDirection: \"row\",\n    alignItems: \"flex-start\",\n\n    justifyContent: \"center\",\n    borderBottomWidth: 1,\n    borderBottomColor: colors.primary700,\n  },\n  textUserName: {\n    ...paragraphBold,\n  },\n  textDate: {\n    ...small,\n    color: colors.primary300,\n  },\n  textMessage: {\n    ...small,\n    color: colors.primary300,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/RoomCard.tsx",
    "content": "import { differenceInMilliseconds, format, isPast, isToday } from \"date-fns\";\nimport React, { useEffect, useState } from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  paragraph,\n  paragraphBold,\n  radius,\n} from \"../constants/dogeStyle\";\nimport { MultipleUserAvatar } from \"./avatars/MultipleUserAvatar\";\nimport { BubbleText } from \"./BubbleText\";\nimport { RoomCardHeading } from \"./RoomCardHeading\";\n\nfunction formatNumber(num: number): string {\n  return Math.abs(num) > 999\n    ? `${Math.sign(num) * Number((Math.abs(num) / 1000).toFixed(1))}K`\n    : `${Math.sign(num) * Math.abs(num)}`;\n}\n\nfunction useScheduleRerender(scheduledFor?: Date) {\n  // same logic stolen from kofta, rerenders\n  // at the scheduleFor date\n  const [, rerender] = useState(0);\n\n  useEffect(() => {\n    if (!scheduledFor) {\n      return;\n    }\n\n    let done = false;\n    const id = setTimeout(() => {\n      done = true;\n      rerender((x) => x + 1);\n    }, differenceInMilliseconds(scheduledFor, new Date()) + 1000);\n\n    return () => {\n      if (!done) {\n        clearTimeout(id);\n      }\n    };\n  }, [scheduledFor]);\n}\n\nexport type RoomCardProps = {\n  style?: ViewStyle;\n  title: string;\n  avatarSrcs: ImageSourcePropType[];\n  subtitle: string;\n  scheduledFor?: Date;\n  listeners: number;\n  tags: React.ReactNode[];\n  onPress?: () => void;\n};\n\nexport const RoomCard: React.FC<RoomCardProps> = ({\n  style,\n  title,\n  avatarSrcs,\n  subtitle,\n  scheduledFor,\n  listeners,\n  tags,\n  onPress,\n}) => {\n  useScheduleRerender(scheduledFor);\n\n  let scheduledForLabel = \"\";\n\n  if (scheduledFor) {\n    if (isToday(scheduledFor)) {\n      scheduledForLabel = format(scheduledFor, \"K:mm a\");\n    } else {\n      scheduledForLabel = format(scheduledFor, \"LLL d\");\n    }\n  }\n\n  const roomLive = !scheduledFor || isPast(scheduledFor);\n\n  return (\n    <TouchableOpacity style={[styles.container, style]} onPress={onPress}>\n      <View style={styles.topContainer}>\n        <View style={styles.topLeftContainer}>\n          <RoomCardHeading\n            icon={\n              roomLive ? undefined : (\n                <Image source={require(\"../assets/images/lg-solid-time.png\")} />\n              )\n            }\n            text={title}\n          />\n          <View style={styles.subtitleContainer}>\n            {avatarSrcs.length > 0 && (\n              <MultipleUserAvatar\n                srcArray={avatarSrcs}\n                size={\"xs\"}\n                translationRatio={1.5}\n              />\n            )}\n            <Text style={styles.subtitle} numberOfLines={1}>\n              {subtitle}\n            </Text>\n          </View>\n        </View>\n        <View style={styles.topRightContainer}>\n          <BubbleText style={{ alignSelf: \"flex-start\" }} live={roomLive}>\n            <Text style={styles.bubbleLabel}>\n              {roomLive ? formatNumber(listeners) : scheduledForLabel}\n            </Text>\n          </BubbleText>\n        </View>\n      </View>\n      <View />\n      {tags && tags.length > 0 && (\n        <View style={styles.tagsContainer}>{tags.map((tag) => tag)}</View>\n      )}\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: colors.primary800,\n    borderRadius: radius.m,\n    padding: 20,\n  },\n  topContainer: {\n    flexDirection: \"row\",\n  },\n  topLeftContainer: {\n    flex: 1,\n  },\n  topRightContainer: {},\n  headingContainer: {\n    flexDirection: \"row\",\n  },\n  subtitleContainer: {\n    flexDirection: \"row\",\n    marginTop: 20,\n  },\n  subtitle: {\n    ...paragraph,\n    color: colors.primary300,\n    flex: 1,\n  },\n  tagsContainer: {\n    flexDirection: \"row\",\n    marginTop: 32,\n    borderRadius: radius.s,\n    overflow: \"hidden\",\n  },\n  bubbleLabel: {\n    ...paragraphBold,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/RoomCardHeading.tsx",
    "content": "import React, { ReactElement } from \"react\";\nimport { StyleSheet, Text } from \"react-native\";\nimport { paragraphBold } from \"../constants/dogeStyle\";\n\nexport interface RoomCardHeadingProps {\n  icon?: ReactElement;\n  text: string;\n}\n\nexport const RoomCardHeading: React.FC<RoomCardHeadingProps> = ({\n  icon,\n  text,\n}) => {\n  return (\n    <>\n      {icon && icon}\n      <Text style={styles.text} numberOfLines={2}>\n        {text}\n      </Text>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    flexDirection: \"row\",\n  },\n  text: {\n    ...paragraphBold,\n    marginRight: 40,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/ScrollViewLoadMore.tsx",
    "content": "import React from \"react\";\nimport { ScrollViewProps } from \"react-native\";\nimport { ScrollView } from \"react-native-gesture-handler\";\nimport { Spinner } from \"./Spinner\";\n\nexport type ScrollViewLoadMoreProps = {\n  scrollViewProps?: ScrollViewProps;\n  shouldLoadMore: boolean;\n  isLoading: boolean;\n  onLoadMore: () => void;\n};\n\nconst isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {\n  const paddingToBottom = 20;\n  return (\n    layoutMeasurement.height + contentOffset.y >=\n    contentSize.height - paddingToBottom\n  );\n};\n\nexport const ScrollViewLoadMore: React.FC<ScrollViewLoadMoreProps> = ({\n  children,\n  scrollViewProps,\n  shouldLoadMore,\n  isLoading,\n  onLoadMore,\n}) => {\n  return (\n    <ScrollView\n      {...scrollViewProps}\n      onScroll={({ nativeEvent }) => {\n        if (isCloseToBottom(nativeEvent) && shouldLoadMore) {\n          onLoadMore();\n        }\n      }}\n      scrollEventThrottle={400}\n    >\n      {children}\n      {isLoading && (\n        <Spinner style={{ alignSelf: \"center\", paddingBottom: 20 }} />\n      )}\n    </ScrollView>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/components/Spinner.tsx",
    "content": "import React from \"react\";\nimport { Animated, Easing, ViewProps } from \"react-native\";\nimport { colors } from \"../constants/dogeStyle\";\n\nexport type SpinnerProps = ViewProps & {\n  size?: \"s\" | \"m\";\n};\n\nexport const Spinner: React.FC<SpinnerProps> = ({ style, size = \"m\" }) => {\n  let spinValue = new Animated.Value(0);\n\n  // First set up animation\n  Animated.loop(\n    Animated.timing(spinValue, {\n      toValue: 1,\n      duration: 1000,\n      easing: Easing.linear, // Easing is an additional import from react-native\n      useNativeDriver: true, // To make use of native driver for performance\n    })\n  ).start();\n\n  // Next, interpolate beginning and end values (in this case 0 and 1)\n  const spin = spinValue.interpolate({\n    inputRange: [0, 1],\n    outputRange: [\"0deg\", \"360deg\"],\n  });\n\n  const isSmall = size === \"s\";\n\n  return (\n    <Animated.View\n      style={[\n        style,\n        {\n          borderWidth: isSmall ? 2 : 4,\n          borderRadius: isSmall ? 6 : 12,\n          borderColor: \"transparent\",\n          height: isSmall ? 12 : 20,\n          width: isSmall ? 12 : 20,\n          borderTopColor: colors.text,\n          borderLeftColor: colors.text,\n          transform: [{ rotate: spin }],\n        },\n      ]}\n    />\n  );\n};\n"
  },
  {
    "path": "pilaf/src/components/Tag.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View, ViewStyle } from \"react-native\";\nimport { colors, radius } from \"../constants/dogeStyle\";\n\ninterface TagProps {\n  style?: ViewStyle;\n  glow?: boolean;\n}\n\nexport const Tag: React.FC<TagProps> = ({ style, children, glow }) => {\n  return (\n    <View style={[styles.container, glow && styles.glow, style]}>\n      {children}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    flexGrow: 0,\n    backgroundColor: colors.primary600,\n    borderRadius: radius.s,\n    paddingHorizontal: 10,\n    // height: 21,\n    borderWidth: 0.5,\n    borderColor: colors.primary600,\n    alignItems: \"center\",\n  },\n  glow: {\n    borderWidth: 0.5,\n    borderColor: colors.accent,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/UpcomingRoomCard.tsx",
    "content": "import { format, isToday, isTomorrow } from \"date-fns\";\nimport React from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport { colors, radius, small } from \"../constants/dogeStyle\";\nimport { MultipleUserAvatar } from \"./avatars/MultipleUserAvatar\";\nimport { RoomCardHeading } from \"./RoomCardHeading\";\n\nconst formattedDate = (scheduledFor: Date) => {\n  if (isToday(scheduledFor)) {\n    return \"TODAY \" + format(scheduledFor, \"K:mm a\");\n  } else if (isTomorrow(scheduledFor)) {\n    return \"TOMMOROW \" + format(scheduledFor, \"K:mm a\");\n  } else {\n    return format(scheduledFor, \"EEE, do MMM, K:mm a\");\n  }\n};\n\nexport interface UserCardProps {\n  avatars: ImageSourcePropType[];\n  speakers: string[];\n}\n\nexport interface ScheduledRoomSummaryCardProps {\n  id: string;\n  scheduledFor: Date;\n  speakersInfo: UserCardProps;\n  title: string;\n}\n\nexport type UpcomingRoomsCardProps = ViewProps & {\n  room: ScheduledRoomSummaryCardProps;\n};\n\nexport const UpcomingRoomCard: React.FC<UpcomingRoomsCardProps> = ({\n  style,\n  room,\n}) => {\n  return (\n    <View style={[styles.container, style]}>\n      <Text style={styles.scheduleFor}>{formattedDate(room.scheduledFor)}</Text>\n      <RoomCardHeading text={room.title} />\n      <View style={styles.content}>\n        <MultipleUserAvatar srcArray={room.speakersInfo.avatars} size={\"xs\"} />\n        <Text style={styles.speakers}>\n          {room.speakersInfo.speakers.join(\", \")}\n        </Text>\n      </View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: colors.primary800,\n    borderRadius: radius.m,\n    paddingHorizontal: 20,\n    paddingVertical: 10,\n  },\n  scheduleFor: {\n    ...small,\n    color: colors.accent,\n  },\n  content: {\n    flexDirection: \"row\",\n    marginTop: 10,\n  },\n  speakers: {\n    ...small,\n    color: colors.primary300,\n    marginLeft: 7,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/UserBadge.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport { colors, fontSize, radius, smallBold } from \"../constants/dogeStyle\";\n\nconst badgeVariants = {\n  primary: colors.primary600,\n  secondary: colors.accent,\n};\n\nexport type UserBadgeProps = ViewProps & {\n  color?: keyof typeof badgeVariants;\n  text?: string;\n  icon?: ImageSourcePropType;\n};\n\nexport const UserBadge: React.FC<UserBadgeProps> = ({\n  text,\n  icon,\n  color = \"primary\",\n}) => {\n  return (\n    <View style={[styles.container, { backgroundColor: badgeVariants[color] }]}>\n      {text && <Text style={styles.text}>{text}</Text>}\n      {icon && (\n        <Image\n          source={icon}\n          height={12}\n          width={12}\n          style={{ height: 12, width: 12 }}\n        />\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    alignSelf: \"baseline\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    height: 16,\n    minWidth: 31,\n    borderRadius: radius.s,\n  },\n  text: {\n    ...smallBold,\n    fontSize: fontSize.xs,\n    lineHeight: 16,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/UserPreview.tsx",
    "content": "import { RoomUser } from \"@dogehouse/kebab\";\nimport { RouteProp, useNavigation } from \"@react-navigation/native\";\nimport React from \"react\";\nimport { ScrollView, StyleSheet, Text, View, ViewProps } from \"react-native\";\nimport { useQueryClient } from \"react-query\";\nimport {\n  colors,\n  fontSize,\n  paragraph,\n  paragraphBold,\n  smallBold,\n} from \"../constants/dogeStyle\";\nimport {\n  RoomChatMessage,\n  useRoomChatStore,\n} from \"../modules/room/chat/useRoomChatStore\";\nimport { RoomStackParamList } from \"../navigation/mainNavigator/RoomNavigator\";\nimport { useConn } from \"../shared-hooks/useConn\";\nimport { useCurrentRoomInfo } from \"../shared-hooks/useCurrentRoomInfo\";\nimport { useTypeSafeMutation } from \"../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeQuery } from \"../shared-hooks/useTypeSafeQuery\";\nimport { useTypeSafeUpdateQuery } from \"../shared-hooks/useTypeSafeUpdateQuery\";\nimport { SingleUserAvatar } from \"./avatars/SingleUserAvatar\";\nimport { Button } from \"./buttons/Button\";\nimport { TitledHeader } from \"./header/TitledHeader\";\nimport { Spinner } from \"./Spinner\";\nimport { Tag } from \"./Tag\";\n\nexport type UserPreviewProps = ViewProps & {\n  message?: RoomChatMessage;\n  id: string;\n  isMe: boolean;\n  iAmCreator: boolean;\n  iAmMod: boolean;\n  isCreator: boolean;\n  roomPermissions?: RoomUser[\"roomPermissions\"];\n  onClosePress: () => void;\n  onVolumeChange?: (volume: number) => void;\n};\n\nexport const UserPreviewInternal: React.FC<UserPreviewProps> = ({\n  style,\n  id,\n  isMe,\n  iAmCreator,\n  iAmMod,\n  isCreator,\n  message,\n  roomPermissions,\n  onClosePress,\n}) => {\n  const updater = useTypeSafeUpdateQuery();\n  const {\n    mutateAsync: setFollow,\n    isLoading: followLoading,\n  } = useTypeSafeMutation(\"follow\");\n  const { mutateAsync: setListener } = useTypeSafeMutation(\"setListener\");\n  const { mutateAsync: changeModStatus } = useTypeSafeMutation(\n    \"changeModStatus\"\n  );\n  const { mutateAsync: changeRoomCreator } = useTypeSafeMutation(\n    \"changeRoomCreator\"\n  );\n  const { mutateAsync: addSpeaker } = useTypeSafeMutation(\"addSpeaker\");\n  const { mutateAsync: deleteRoomChatMessage } = useTypeSafeMutation(\n    \"deleteRoomChatMessage\"\n  );\n  const { mutateAsync: blockFromRoom } = useTypeSafeMutation(\"blockFromRoom\");\n  const { mutateAsync: banFromRoomChat } = useTypeSafeMutation(\n    \"banFromRoomChat\"\n  );\n\n  const { data, isLoading } = useTypeSafeQuery([\"getUserProfile\", id], {}, [\n    id,\n  ]);\n\n  const bannedUserIdMap = useRoomChatStore((s) => s.bannedUserIdMap);\n\n  const queryClient = useQueryClient();\n\n  if (isLoading) {\n    return (\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: colors.primary900,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n        }}\n      >\n        <Spinner />\n      </View>\n    );\n  }\n\n  if (!data) {\n    return (\n      <View style={{ flex: 1, justifyContent: \"center\", alignItems: \"center\" }}>\n        <Spinner />\n      </View>\n    );\n  }\n\n  const canDoModStuffOnThisUser = !isMe && (iAmCreator || iAmMod) && !isCreator;\n\n  return (\n    <View style={[style, styles.container]}>\n      <TitledHeader showBackButton={true} title={data.displayName} />\n      <ScrollView contentContainerStyle={{ flex: 1, alignItems: \"center\" }}>\n        <SingleUserAvatar\n          src={{ uri: data.avatarUrl }}\n          isOnline={data.online}\n          style={styles.avatar}\n        />\n        <Text style={styles.displayName}>{data.username}</Text>\n        <View style={styles.tagsContainer}>\n          {[\"DC\", \"DS\"].map((tag) => (\n            <Tag\n              key={tag}\n              style={{ marginLeft: 2.5, marginRight: 2.5, height: 16 }}\n            >\n              <Text\n                style={{ ...smallBold, fontSize: fontSize.xs, lineHeight: 16 }}\n              >\n                {tag}\n              </Text>\n            </Tag>\n          ))}\n          {data.followsYou && (\n            <Tag style={{ marginLeft: 5, height: 16 }}>\n              <Text\n                style={{\n                  ...smallBold,\n                  fontSize: fontSize.xs,\n                  color: colors.primary300,\n                  lineHeight: 16,\n                }}\n              >\n                {\"Follows you\"}\n              </Text>\n            </Tag>\n          )}\n        </View>\n        <View style={styles.followInfoContainer}>\n          <View\n            style={{\n              flexDirection: \"row\",\n              flex: 1,\n              justifyContent: \"flex-end\",\n              marginRight: 10,\n            }}\n          >\n            <Text style={{ ...paragraphBold }}>{data.numFollowers}</Text>\n            <Text\n              style={{ ...paragraph, color: colors.primary300, marginLeft: 6 }}\n            >\n              {\"followers\"}\n            </Text>\n          </View>\n          <View\n            style={{\n              flexDirection: \"row\",\n              flex: 1,\n              marginLeft: 10,\n            }}\n          >\n            <Text\n              style={{\n                ...paragraphBold,\n              }}\n            >\n              {data.numFollowing}\n            </Text>\n            <Text\n              style={{ ...paragraph, color: colors.primary300, marginLeft: 6 }}\n            >\n              {\"following\"}\n            </Text>\n          </View>\n        </View>\n        {data.bio ? (\n          <Text\n            style={{\n              ...paragraph,\n              color: colors.primary300,\n              paddingHorizontal: 40,\n              marginTop: 10,\n              textAlign: \"center\",\n            }}\n            numberOfLines={3}\n          >\n            {data.bio}\n          </Text>\n        ) : (\n          <></>\n        )}\n        {/* TODO: Should be data.link or something */}\n        <Text style={{ ...paragraphBold, color: colors.accent, marginTop: 10 }}>\n          https://mapwize.io\n        </Text>\n        <View style={styles.followDMContainer}>\n          <Button\n            iconSrc={require(\"../assets/images/md-person-add.png\")}\n            title={data.youAreFollowing ? \"Unfollow\" : \"Follow\"}\n            style={{\n              flex: 1,\n              alignSelf: \"center\",\n              paddingHorizontal: undefined,\n            }}\n            color={data.youAreFollowing ? \"secondary\" : \"primary\"}\n            onPress={async () => {\n              await setFollow([data.id, !data.youAreFollowing]);\n              queryClient.invalidateQueries(\"getMyFollowing\");\n              updater([\"getUserProfile\", data.id], (u) =>\n                !u\n                  ? u\n                  : {\n                      ...u,\n                      numFollowers:\n                        u.numFollowers + (data.youAreFollowing ? -1 : 1),\n                      youAreFollowing: !data.youAreFollowing,\n                    }\n              );\n            }}\n          />\n          <Button\n            title={\"Send DM\"}\n            iconSrc={require(\"../assets/images/header/sm-solid-messages.png\")}\n            style={{\n              marginLeft: 10,\n              flex: 1,\n              alignSelf: \"center\",\n              paddingHorizontal: undefined,\n            }}\n            color={\"secondary\"}\n          />\n        </View>\n        <View style={styles.controlsContainer}>\n          {/* <Text\n            style={{\n              ...paragraph,\n              color: colors.primary300,\n              marginTop: 20,\n              alignSelf: \"center\",\n            }}\n          >\n            Volume\n          </Text>\n          <View style={styles.sliderContainer}>\n            <Image\n              source={require(\"../assets/images/ios-volume-off.png\")}\n              style={{ marginRight: 15 }}\n            />\n            <Slider\n              style={{ flex: 1 }}\n              thumbStyle={{ backgroundColor: colors.primary100 }}\n              trackStyle={{ backgroundColor: colors.primary300 }}\n              onValueChange={(value) => setVolume(id, value)}\n              value={100}\n              minimumValue={0}\n              maximumValue={200}\n              minimumTrackTintColor={colors.primary300}\n              maximumTrackTintColor={colors.primary300}\n            />\n            <Image\n              source={require(\"../assets/images/ios-volume-high.png\")}\n              style={{ marginLeft: 15 }}\n            />\n          </View> */}\n          {canDoModStuffOnThisUser && (\n            <>\n              <Text\n                style={{\n                  ...paragraph,\n                  color: colors.primary300,\n                  marginTop: 15,\n                  alignSelf: \"center\",\n                }}\n              >\n                Manage\n              </Text>\n              {!isMe && iAmCreator && (\n                <View\n                  style={{\n                    marginTop: 15,\n                    flexDirection: \"row\",\n                  }}\n                >\n                  <Button\n                    title={\n                      roomPermissions?.isMod\n                        ? \"Remove moderator\"\n                        : \"Make moderator\"\n                    }\n                    style={{\n                      marginHorizontal: 40,\n                      padding: null,\n                      flexGrow: 1,\n                    }}\n                    color=\"secondary\"\n                    onPress={() => {\n                      changeModStatus([id, !roomPermissions?.isMod]);\n                      onClosePress();\n                    }}\n                  />\n                </View>\n              )}\n              {canDoModStuffOnThisUser &&\n                !roomPermissions?.isSpeaker &&\n                roomPermissions?.askedToSpeak && (\n                  <View\n                    style={{\n                      marginTop: 10,\n                      flexDirection: \"row\",\n                    }}\n                  >\n                    <Button\n                      title={\"Add as speaker\"}\n                      style={{\n                        marginHorizontal: 40,\n                        padding: null,\n                        flexGrow: 1,\n                      }}\n                      color=\"secondary\"\n                      onPress={() => {\n                        addSpeaker([id]);\n                        onClosePress();\n                      }}\n                    />\n                  </View>\n                )}\n              {canDoModStuffOnThisUser && roomPermissions?.isSpeaker && (\n                <View\n                  style={{\n                    marginTop: 10,\n                    flexDirection: \"row\",\n                  }}\n                >\n                  <Button\n                    title={\"Move to listener\"}\n                    style={{\n                      marginHorizontal: 40,\n                      padding: null,\n                      flexGrow: 1,\n                    }}\n                    color=\"secondary\"\n                    onPress={() => {\n                      setListener([id]);\n                      onClosePress();\n                    }}\n                  />\n                </View>\n              )}\n              {!!message && (\n                <View\n                  style={{\n                    marginTop: 10,\n                    flexDirection: \"row\",\n                  }}\n                >\n                  <Button\n                    title={\"Delete message\"}\n                    style={{\n                      marginHorizontal: 40,\n                      padding: null,\n                      flexGrow: 1,\n                    }}\n                    color=\"secondary\"\n                    onPress={() => {\n                      deleteRoomChatMessage([message.userId, message.id]);\n                      onClosePress();\n                    }}\n                  />\n                </View>\n              )}\n              {canDoModStuffOnThisUser &&\n                !(id in bannedUserIdMap) &&\n                (iAmCreator || !roomPermissions?.isMod) && (\n                  <View\n                    style={{\n                      marginTop: 10,\n                      flexDirection: \"row\",\n                    }}\n                  >\n                    <Button\n                      title={\"Kick from room\"}\n                      style={{\n                        marginHorizontal: 40,\n                        padding: null,\n                        flexGrow: 1,\n                      }}\n                      color=\"secondary\"\n                      onPress={() => {}}\n                    />\n                  </View>\n                )}\n              {canDoModStuffOnThisUser &&\n                !(id in bannedUserIdMap) &&\n                (iAmCreator || !roomPermissions?.isMod) && (\n                  <View\n                    style={{\n                      marginTop: 10,\n                      paddingHorizontal: 40,\n                      flexDirection: \"row\",\n                    }}\n                  >\n                    <Button\n                      title={\"Ban from chat\"}\n                      style={{\n                        paddingHorizontal: null,\n                        flexGrow: 1,\n                      }}\n                      color=\"secondary\"\n                      onPress={() => {\n                        banFromRoomChat([id]);\n                        onClosePress();\n                      }}\n                    />\n                    <Button\n                      title={\"Ban from room\"}\n                      style={{\n                        paddingHorizontal: null,\n                        flexGrow: 1,\n                        marginLeft: 10,\n                      }}\n                      color=\"secondary\"\n                      onPress={() => {\n                        blockFromRoom([id]);\n                        onClosePress();\n                      }}\n                    />\n                  </View>\n                )}\n            </>\n          )}\n        </View>\n      </ScrollView>\n    </View>\n  );\n};\n\ntype UserPreviewRoutePageProp = RouteProp<\n  RoomStackParamList,\n  \"RoomUserPreview\"\n>;\n\ntype UserPreviewRouteProp = {\n  route: UserPreviewRoutePageProp;\n  message?: RoomChatMessage;\n};\n\nexport const UserPreview: React.FC<UserPreviewRouteProp> = ({ route }) => {\n  const room = route.params.data.room;\n  const users = route.params.data.users;\n  const userId = route.params.userId;\n  const message = route.params.message;\n  const navigation = useNavigation();\n  const { isCreator: iAmCreator, isMod } = useCurrentRoomInfo();\n  const conn = useConn();\n  return (\n    <>\n      <View style={{ flexGrow: 1 }}>\n        <UserPreviewInternal\n          id={userId}\n          isCreator={room.creatorId === userId}\n          roomPermissions={users.find((u) => u.id === userId)?.roomPermissions}\n          iAmCreator={iAmCreator}\n          isMe={conn.user.id === userId}\n          iAmMod={isMod}\n          message={message}\n          onClosePress={() => navigation.goBack()}\n        />\n      </View>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: colors.primary900,\n  },\n  avatar: { marginBottom: 7, marginTop: 13 },\n  displayName: { ...paragraphBold },\n  username: { ...paragraph, color: colors.primary300, marginLeft: 5 },\n  tagsContainer: { flexDirection: \"row\", marginTop: 7 },\n  followInfoContainer: {\n    flexDirection: \"row\",\n    marginTop: 17,\n  },\n  followDMContainer: {\n    flexDirection: \"row\",\n    marginTop: 23,\n    paddingHorizontal: 25,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  controlsContainer: {\n    marginTop: 30,\n    backgroundColor: colors.primary800,\n    alignItems: \"flex-start\",\n    paddingBottom: 20,\n  },\n  sliderContainer: {\n    marginTop: 10,\n    flexDirection: \"row\",\n    // width: \"100%\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    paddingHorizontal: 40,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/accountModal/AccountModalContent.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { colors, radius } from \"../../constants/dogeStyle\";\nimport { AccountModalRow } from \"./AccountModalRow\";\n\nconst separator = (\n  <View style={{ backgroundColor: colors.primary700, height: 1 }} />\n);\n\nexport type AccountModalContentProps = {\n  onPress: (pageName: string) => void;\n};\n\nexport const AccountModalContent: React.FC<AccountModalContentProps> = ({\n  onPress,\n}) => {\n  const inset = useSafeAreaInsets();\n  return (\n    <View style={[styles.content, { paddingBottom: inset.bottom }]}>\n      <View style={styles.dragIndicator} />\n      <AccountModalRow\n        icon={require(\"../../assets/images/account/sm-solid-user.png\")}\n        title={\"Profile\"}\n        onPress={() => onPress(\"Profile\")}\n      />\n      {separator}\n      <AccountModalRow\n        icon={require(\"../../assets/images/account/sm-solid-settings.png\")}\n        title={\"Settings\"}\n        onPress={() => onPress(\"Settings\")}\n      />\n      {separator}\n      <AccountModalRow\n        icon={require(\"../../assets/images/account/wallet.png\")}\n        title={\"Wallet\"}\n        onPress={() => onPress(\"Wallet\")}\n      />\n      {separator}\n      <AccountModalRow\n        icon={require(\"../../assets/images/account/sm-outline-globe.png\")}\n        title={\"Language\"}\n        onPress={() => onPress(\"Language\")}\n      />\n      {separator}\n      <AccountModalRow\n        icon={require(\"../../assets/images/account/sm-solid-help.png\")}\n        title={\"Help\"}\n        onPress={() => onPress(\"Help\")}\n      />\n      {separator}\n      <AccountModalRow\n        icon={require(\"../../assets/images/account/sm-solid-bug.png\")}\n        title={\"Report a bug\"}\n        onPress={() => onPress(\"ReportBug\")}\n      />\n      <View />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  content: {\n    backgroundColor: colors.primary800,\n    borderTopLeftRadius: radius.m,\n    borderTopRightRadius: radius.m,\n  },\n  dragIndicator: {\n    width: 40,\n    height: 4,\n    borderRadius: 2,\n    backgroundColor: colors.primary300,\n    alignSelf: \"center\",\n    marginVertical: 12,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/accountModal/AccountModalRow.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n} from \"react-native\";\nimport { paragraph } from \"../../constants/dogeStyle\";\n\nexport interface AccountModalRowProps {\n  icon: ImageSourcePropType;\n  title: string;\n  onPress: () => void;\n}\n\nexport const AccountModalRow: React.FC<AccountModalRowProps> = ({\n  icon,\n  title,\n  onPress,\n}) => {\n  return (\n    <TouchableOpacity onPress={() => onPress()} style={styles.content}>\n      <Image source={icon} />\n      <Text style={styles.title}>{title}</Text>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  content: {\n    flexDirection: \"row\",\n    paddingHorizontal: 25,\n    paddingVertical: 16,\n    alignItems: \"center\",\n  },\n  title: {\n    ...paragraph,\n    marginLeft: 16,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/avatars/MultipleUserAvatar.tsx",
    "content": "import React from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleProp,\n  StyleSheet,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { colors } from \"../../constants/dogeStyle\";\nimport { SingleUserAvatar, singleUserAvatarSize } from \"./SingleUserAvatar\";\n\ninterface MultipleUserAvatarProps {\n  style?: StyleProp<ViewStyle>;\n  srcArray: ImageSourcePropType[];\n  size?: \"default\" | \"md\" | \"sm\" | \"xs\";\n  translationRatio?: number;\n}\n\nexport const MultipleUserAvatar: React.FC<MultipleUserAvatarProps> = ({\n  srcArray,\n  style,\n  size = \"sm\",\n  translationRatio = 2,\n}) => {\n  const singleAvatarSize = singleUserAvatarSize[size];\n  return (\n    <View\n      style={[\n        style,\n        {\n          width:\n            ((srcArray.length + 1) * singleAvatarSize) / translationRatio + 4,\n          height: singleAvatarSize + 4,\n        },\n      ]}\n    >\n      {srcArray.slice(0, 3).map((src, i) => {\n        return (\n          <View\n            key={\"\" + src + i}\n            style={[\n              styles.singleAvatarContainer,\n              {\n                left: (i * singleAvatarSize) / translationRatio,\n                borderRadius: (singleAvatarSize + 4) / 2,\n                zIndex: 100 - i,\n              },\n            ]}\n          >\n            <SingleUserAvatar src={src} size={size} />\n          </View>\n        );\n      })}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  singleAvatarContainer: {\n    position: \"absolute\",\n    borderWidth: 2,\n    borderColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/avatars/RoomAvatar.tsx",
    "content": "import React from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleProp,\n  Text,\n  TouchableOpacity,\n  ViewStyle,\n} from \"react-native\";\nimport { small } from \"../../constants/dogeStyle\";\nimport { SingleUserAvatar } from \"./SingleUserAvatar\";\n\ninterface RoomAvatarProps {\n  style?: StyleProp<ViewStyle>;\n  src: ImageSourcePropType;\n  username: string;\n  flair?: React.ReactNode;\n  muted?: boolean;\n  activeSpeaker?: boolean;\n  onPress?: () => void;\n}\n\nexport const RoomAvatar: React.FC<RoomAvatarProps> = ({\n  src,\n  username,\n  style,\n  flair,\n  muted,\n  activeSpeaker,\n  onPress,\n}) => {\n  return (\n    <TouchableOpacity\n      style={[style, { alignItems: \"center\" }]}\n      onPress={onPress}\n    >\n      <SingleUserAvatar\n        src={src}\n        size={\"md\"}\n        muted={muted}\n        activeSpeaker={activeSpeaker}\n      />\n      <Text\n        style={{ ...small, maxWidth: 80, textAlign: \"center\" }}\n        numberOfLines={1}\n      >\n        {username}\n        {flair}\n      </Text>\n    </TouchableOpacity>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/components/avatars/SingleUserAvatar.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleProp,\n  StyleSheet,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { colors } from \"../../constants/dogeStyle\";\n\ninterface SingleUserAvatarProps {\n  style?: StyleProp<ViewStyle>;\n  src: ImageSourcePropType;\n  size?: \"default\" | \"sm\" | \"xxs\" | \"xs\" | \"md\";\n  isOnline?: boolean;\n  muted?: boolean;\n  activeSpeaker?: boolean;\n}\n\nexport const singleUserAvatarSize = {\n  default: 80,\n  md: 50,\n  sm: 40,\n  xxs: 30,\n  xs: 20,\n};\n\nconst indicatorSize = {\n  default: 23,\n  md: 14,\n  sm: 12,\n  xxs: 8,\n  xs: 6,\n};\n\nexport const SingleUserAvatar: React.FC<SingleUserAvatarProps> = ({\n  src,\n  size = \"default\",\n  style,\n  isOnline = false,\n  muted = false,\n  activeSpeaker = false,\n}) => {\n  return (\n    <View\n      style={[\n        style,\n        styles[size + \"Avatar\"],\n        activeSpeaker\n          ? {\n              shadowColor: colors.accent,\n              shadowRadius: 10,\n              shadowOpacity: 1,\n              shadowOffset: { height: 0.5 },\n            }\n          : {},\n      ]}\n    >\n      <Image\n        source={src}\n        style={[\n          styles[size + \"Avatar\"],\n          activeSpeaker && { borderWidth: 2, borderColor: colors.accent },\n        ]}\n      />\n      {isOnline && (\n        <View style={[styles[size + \"Indicator\"], styles.indicator]} />\n      )}\n      {muted && (\n        <View\n          style={[\n            styles[size + \"Indicator\"],\n            styles.indicator,\n            {\n              alignItems: \"center\",\n              justifyContent: \"center\",\n              backgroundColor: colors.primary900,\n              height: indicatorSize[size] + 4,\n              width: indicatorSize[size] + 4,\n              borderRadius: (indicatorSize[size] + 4) / 2,\n              padding: 2,\n            },\n          ]}\n        >\n          <Image\n            source={require(\"../../assets/images/SolidMicrophoneOff.png\")}\n            style={{\n              height: indicatorSize[size] - 4,\n              width: indicatorSize[size] - 4,\n              tintColor: colors.accent,\n            }}\n          />\n        </View>\n      )}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  defaultAvatar: {\n    height: singleUserAvatarSize.default,\n    width: singleUserAvatarSize.default,\n    borderRadius: singleUserAvatarSize.default / 2,\n  },\n  mdAvatar: {\n    height: singleUserAvatarSize.md,\n    width: singleUserAvatarSize.md,\n    borderRadius: singleUserAvatarSize.md / 2,\n  },\n  smAvatar: {\n    height: singleUserAvatarSize.sm,\n    width: singleUserAvatarSize.sm,\n    borderRadius: singleUserAvatarSize.sm / 2,\n  },\n  xxsAvatar: {\n    height: singleUserAvatarSize.xxs,\n    width: singleUserAvatarSize.xxs,\n    borderRadius: singleUserAvatarSize.xxs / 2,\n  },\n  xsAvatar: {\n    height: singleUserAvatarSize.xs,\n    width: singleUserAvatarSize.xs,\n    borderRadius: singleUserAvatarSize.xs / 2,\n  },\n  indicator: {\n    position: \"absolute\",\n    borderColor: colors.primary900,\n    backgroundColor: colors.accent,\n  },\n  defaultIndicator: {\n    width: 24,\n    height: 24,\n    right: 2,\n    bottom: -4,\n    borderWidth: 4,\n    borderRadius: 12,\n  },\n  xxsIndicator: {\n    width: 8,\n    height: 8,\n    right: 1,\n    bottom: -1,\n    borderWidth: 1,\n    borderRadius: 4,\n  },\n  mdIndicator: {\n    width: 14,\n    height: 14,\n    right: 2,\n    bottom: -2,\n    borderWidth: 2,\n    borderRadius: 7,\n  },\n  smIndicator: {\n    width: 12,\n    height: 12,\n    right: 2,\n    bottom: -2,\n    borderWidth: 2,\n    borderRadius: 6,\n  },\n  xsIndicator: {\n    width: 6,\n    height: 6,\n    right: 0,\n    bottom: -1,\n    borderWidth: 1,\n    borderRadius: 3,\n  },\n  mdMic: {\n    position: \"absolute\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    width: 18,\n    height: 18,\n    right: 2,\n    bottom: -2,\n    backgroundColor: colors.primary900,\n    borderRadius: 9,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/bottomBar/CreateRoomButton.tsx",
    "content": "import React, { useState } from \"react\";\nimport { StyleSheet, TouchableOpacity } from \"react-native\";\nimport Modal from \"react-native-modal\";\nimport { CreateRoomPage } from \"../../pages/CreateRoomPage\";\n\nexport const CreateRoomButton: React.FC = (props) => {\n  const [modalVisible, setModalVisible] = useState(false);\n  return (\n    <>\n      <TouchableOpacity {...props} onPress={() => setModalVisible(true)} />\n      <Modal\n        backdropOpacity={0.8}\n        isVisible={modalVisible}\n        onBackdropPress={() => setModalVisible(false)}\n        style={styles.contentView}\n      >\n        <CreateRoomPage onRequestClose={() => setModalVisible(false)} />\n      </Modal>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  contentView: {\n    justifyContent: \"flex-end\",\n    margin: 0,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/buttons/Button.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  ViewProps,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  paragraphBold,\n  radius,\n  smallBold,\n} from \"../../constants/dogeStyle\";\nimport { Spinner } from \"../Spinner\";\n\nexport type ButtonProps = ViewProps & {\n  iconSrc?: ImageSourcePropType;\n  size?: \"big\" | \"small\";\n  color?: \"primary\" | \"secondary\";\n  loading?: boolean;\n  disabled?: boolean;\n  title;\n  onPress?: () => void;\n};\n\nexport const Button: React.FC<ButtonProps> = ({\n  size = \"big\",\n  color = \"primary\",\n  disabled = false,\n  loading = false,\n  iconSrc,\n  title,\n  ...props\n}) => {\n  let colorStyle = color === \"primary\" ? primaryStyle : secondaryStyle;\n  let big = size === \"big\";\n  let styleSize = big ? bigStyle : smallStyle;\n\n  return (\n    <TouchableOpacity\n      style={[\n        disabled ? colorStyle.disabled : colorStyle.default,\n        styleSize,\n        props.style,\n      ]}\n      onPress={props.onPress}\n      disabled={disabled || loading}\n    >\n      {iconSrc && (\n        <Image\n          source={iconSrc}\n          style={[\n            { tintColor: colors.text, marginRight: 10, width: 20 },\n            loading && { opacity: 0 },\n          ]}\n        />\n      )}\n      <Text\n        style={[\n          big ? colorStyle.text : colorStyle.textSmall,\n          loading && { opacity: 0 },\n        ]}\n      >\n        {title}\n      </Text>\n      {loading && (\n        <Spinner style={{ position: \"absolute\" }} size={big ? \"m\" : \"s\"} />\n      )}\n    </TouchableOpacity>\n  );\n};\n\nconst containerBase: ViewStyle = {\n  alignSelf: \"baseline\",\n  flexDirection: \"row\",\n  justifyContent: \"center\",\n  alignItems: \"center\",\n};\n\nconst primaryStyle = StyleSheet.create({\n  text: {\n    ...paragraphBold,\n  },\n  textSmall: {\n    ...smallBold,\n  },\n  default: {\n    ...containerBase,\n    backgroundColor: colors.accent,\n  },\n  disabled: {\n    ...containerBase,\n    backgroundColor: colors.accentDisabled,\n  },\n});\n\nconst secondaryStyle = StyleSheet.create({\n  text: {\n    ...paragraphBold,\n  },\n  textSmall: {\n    ...smallBold,\n  },\n  default: {\n    ...containerBase,\n    backgroundColor: colors.primary700,\n  },\n  disabled: {\n    ...containerBase,\n    backgroundColor: colors.primary300,\n  },\n});\n\nconst smallStyle: ViewStyle = {\n  borderRadius: radius.s,\n  paddingHorizontal: 10,\n};\n\nconst bigStyle: ViewStyle = {\n  borderRadius: radius.m,\n  paddingHorizontal: 38,\n  paddingVertical: 8,\n  height: 38,\n};\n"
  },
  {
    "path": "pilaf/src/components/buttons/IconButton.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleProp,\n  StyleSheet,\n  TouchableOpacity,\n  ViewStyle,\n} from \"react-native\";\nimport { colors } from \"../../constants/dogeStyle\";\n\ninterface IconButtonProps {\n  style?: StyleProp<ViewStyle>;\n  icon: ImageSourcePropType;\n  iconColor?: string;\n  onPress: () => void;\n}\n\nexport const IconButton: React.FC<IconButtonProps> = ({\n  style,\n  onPress,\n  icon,\n  iconColor = colors.text,\n}) => {\n  return (\n    <TouchableOpacity style={[style, styles.container]} onPress={onPress}>\n      <Image source={icon} style={{ tintColor: iconColor }} />\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/buttons/SignInButton.tsx",
    "content": "import React from \"react\";\nimport {\n  Alert,\n  Image,\n  Linking,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  ViewProps,\n} from \"react-native\";\nimport InAppBrowser from \"react-native-inappbrowser-reborn\";\nimport { colors, paragraphBold, radius } from \"../../constants/dogeStyle\";\nimport { apiBaseUrl } from \"../../constants/env\";\n\ntype Provider = \"google\" | \"twitter\" | \"github\" | \"apple\";\n\nconst onPress = async (provider: Provider) => {\n  try {\n    const url =\n      apiBaseUrl +\n      \"/auth/\" +\n      provider +\n      \"/web?redirect_after_base=dogehouse://home\";\n    if (await InAppBrowser.isAvailable()) {\n      await InAppBrowser.open(url, {\n        // iOS Properties\n        dismissButtonStyle: \"cancel\",\n        preferredBarTintColor: colors.primary900,\n        preferredControlTintColor: \"white\",\n        readerMode: false,\n        animated: true,\n        modalPresentationStyle: \"fullScreen\",\n        modalTransitionStyle: \"coverVertical\",\n        modalEnabled: true,\n        enableBarCollapsing: false,\n        // Android Properties\n        showTitle: true,\n        toolbarColor: colors.primary900,\n        secondaryToolbarColor: colors.text,\n        enableUrlBarHiding: true,\n        enableDefaultShare: true,\n        forceCloseOnRedirection: false,\n        animations: {\n          startEnter: \"slide_in_right\",\n          startExit: \"slide_out_left\",\n          endEnter: \"slide_in_left\",\n          endExit: \"slide_out_right\",\n        },\n      });\n    } else {\n      Linking.openURL(url);\n    }\n  } catch (error) {\n    Alert.alert(error.message);\n  }\n};\n\nexport type SignInButtonProps = ViewProps & {\n  provider: Provider;\n};\n\nconst imageSrcs = {\n  github: require(\"../../assets/images/github.png\"),\n  google: require(\"../../assets/images/google.png\"),\n  twitter: require(\"../../assets/images/twitter.png\"),\n  apple: require(\"../../assets/images/apple.png\"),\n};\n\nconst title = {\n  github: \"Sign in with Github\",\n  google: \"Sign in with Google\",\n  twitter: \"Sign in with Twitter\",\n  apple: \"Sign in with Apple\",\n};\n\nexport const SignInButton: React.FC<SignInButtonProps> = ({\n  style,\n  provider,\n}) => {\n  return (\n    <TouchableOpacity\n      onPress={() => onPress(provider)}\n      style={[style, styles.button]}\n    >\n      <Image source={imageSrcs[provider]} style={{ tintColor: colors.text }} />\n      <Text style={styles.title}>{title[provider]}</Text>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  button: {\n    flexDirection: \"row\",\n    backgroundColor: colors.primary700,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    borderRadius: radius.m,\n    paddingRight: 40,\n  },\n  title: {\n    ...paragraphBold,\n    marginLeft: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/buttons/SigninWithGithub.tsx",
    "content": "import React from \"react\";\nimport {\n  Alert,\n  Linking,\n  StyleProp,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  ViewStyle,\n} from \"react-native\";\nimport InAppBrowser from \"react-native-inappbrowser-reborn\";\nimport { colors, fontFamily, radius } from \"../../constants/dogeStyle\";\nimport { apiBaseUrl } from \"../../constants/env\";\n\nconst signinWithGithub = async () => {\n  try {\n    const url =\n      apiBaseUrl + \"/auth/github/web?redirect_after_base=dogehouse://home\";\n    if (await InAppBrowser.isAvailable()) {\n      await InAppBrowser.open(url, {\n        // iOS Properties\n        dismissButtonStyle: \"cancel\",\n        preferredBarTintColor: colors.primary900,\n        preferredControlTintColor: \"white\",\n        readerMode: false,\n        animated: true,\n        modalPresentationStyle: \"fullScreen\",\n        modalTransitionStyle: \"coverVertical\",\n        modalEnabled: true,\n        enableBarCollapsing: false,\n        // Android Properties\n        showTitle: true,\n        toolbarColor: colors.primary900,\n        secondaryToolbarColor: colors.text,\n        enableUrlBarHiding: true,\n        enableDefaultShare: true,\n        forceCloseOnRedirection: false,\n        animations: {\n          startEnter: \"slide_in_right\",\n          startExit: \"slide_out_left\",\n          endEnter: \"slide_in_left\",\n          endExit: \"slide_out_right\",\n        },\n      });\n    } else {\n      Linking.openURL(url);\n    }\n  } catch (error) {\n    Alert.alert(error.message);\n  }\n};\n\ninterface SigninWithGithubButtonProps {\n  style: StyleProp<ViewStyle>;\n}\n\nexport const SigninWithGithubButton: React.FC<SigninWithGithubButtonProps> = (\n  props\n) => {\n  return (\n    <TouchableOpacity\n      onPress={signinWithGithub}\n      style={[props.style, styles.button]}\n    >\n      <Text style={styles.title}>{\"Sign in with github\"}</Text>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  button: {\n    backgroundColor: colors.black,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    borderRadius: radius.m,\n    padding: 16,\n  },\n  title: {\n    color: colors.text,\n    fontFamily: fontFamily.semiBold,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/header/Header.tsx",
    "content": "import { useNavigation } from \"@react-navigation/core\";\nimport React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { colors } from \"../../constants/dogeStyle\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { IconButton } from \"../buttons/IconButton\";\nimport { ProfileButton } from \"./ProfileButton\";\n\nexport const Header: React.FC = () => {\n  const navigation = useNavigation();\n  const inset = useSafeAreaInsets();\n  const conn = useConn();\n  return (\n    <View style={[styles.container, { paddingTop: inset.top }]}>\n      <View style={styles.leftContainer}>\n        <ProfileButton icon={{ uri: conn.user.avatarUrl }} />\n      </View>\n      <View style={styles.rightContainer}>\n        <IconButton\n          icon={require(\"../../assets/images/header/sm-solid-notification.png\")}\n          style={{ marginLeft: 30 }}\n          onPress={() => navigation.navigate(\"Notifications\")}\n        />\n        <IconButton\n          icon={require(\"../../assets/images/header/sm-solid-messages.png\")}\n          style={{ marginLeft: 30 }}\n          onPress={() => navigation.navigate(\"Messages\")}\n        />\n        <IconButton\n          icon={require(\"../../assets/images/header/sm-solid-search.png\")}\n          style={{ marginLeft: 30 }}\n          onPress={() => navigation.navigate(\"Search\")}\n        />\n      </View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    backgroundColor: colors.primary900,\n    flexDirection: \"row\",\n    paddingHorizontal: 25,\n  },\n  leftContainer: {\n    height: 70,\n    justifyContent: \"center\",\n  },\n  rightContainer: {\n    flexDirection: \"row\",\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/header/HeaderBase.tsx",
    "content": "import { useNavigation } from \"@react-navigation/core\";\nimport React from \"react\";\nimport {\n  Image,\n  StyleSheet,\n  TouchableOpacity,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nexport type HeaderBaseProps = ViewProps & {\n  showBackButton?: boolean;\n};\n\nexport const HeaderBase: React.FC<HeaderBaseProps> = ({\n  showBackButton = true,\n  children,\n}) => {\n  const navigation = useNavigation();\n  const inset = useSafeAreaInsets();\n  return (\n    <View style={[styles.container, { paddingTop: inset.top }]}>\n      {showBackButton && (\n        <TouchableOpacity\n          style={styles.leftContainer}\n          onPress={() => navigation.goBack()}\n        >\n          <Image\n            source={require(\"../../assets/images/header/sm-solid-caret-right.png\")}\n          />\n        </TouchableOpacity>\n      )}\n      {children}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    alignItems: \"center\",\n    backgroundColor: colors.primary900,\n    flexDirection: \"row\",\n  },\n  leftContainer: {\n    paddingLeft: 25,\n    height: 70,\n    justifyContent: \"center\",\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/header/ProfileButton.tsx",
    "content": "import { useNavigation } from \"@react-navigation/core\";\nimport React, { useState } from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleProp,\n  StyleSheet,\n  TouchableOpacity,\n  ViewStyle,\n} from \"react-native\";\nimport Modal from \"react-native-modal\";\nimport { AccountModalContent } from \"../accountModal/AccountModalContent\";\nimport { SingleUserAvatar } from \"../avatars/SingleUserAvatar\";\n\ninterface ProfileButtonProps {\n  style?: StyleProp<ViewStyle>;\n  icon: ImageSourcePropType;\n}\n\nexport const ProfileButton: React.FC<ProfileButtonProps> = (props) => {\n  const navigation = useNavigation();\n  const [modalVisible, setModalVisible] = useState(false);\n  return (\n    <>\n      <TouchableOpacity onPress={() => setModalVisible(true)}>\n        <SingleUserAvatar src={props.icon} size={\"xxs\"} isOnline={true} />\n      </TouchableOpacity>\n      <Modal\n        backdropOpacity={0.8}\n        isVisible={modalVisible}\n        onBackdropPress={() => setModalVisible(false)}\n        style={styles.contentView}\n        onSwipeComplete={() => setModalVisible(false)}\n        swipeDirection=\"down\"\n        swipeThreshold={50}\n      >\n        <AccountModalContent\n          onPress={(pageName: string) => {\n            setModalVisible(false);\n            navigation.navigate(pageName);\n          }}\n        />\n      </Modal>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  contentView: {\n    justifyContent: \"flex-end\",\n    flex: 1,\n    margin: 0,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/header/RoomHeader.tsx",
    "content": "import { useNavigation } from \"@react-navigation/core\";\nimport React from \"react\";\nimport { Image, StyleSheet, Text, TouchableOpacity, View } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport {\n  colors,\n  h4,\n  paragraphBold,\n  small,\n  smallBold,\n} from \"../../constants/dogeStyle\";\n\ntype RoomHeaderProps = {\n  onLeavePress: () => void;\n  onTitlePress: () => void;\n  roomTitle: string;\n  roomSubtitle: string;\n};\n\nexport const RoomHeader: React.FC<RoomHeaderProps> = ({\n  onLeavePress,\n  onTitlePress,\n  roomTitle,\n  roomSubtitle,\n}) => {\n  const navigation = useNavigation();\n  const inset = useSafeAreaInsets();\n\n  return (\n    <View style={[styles.container, { paddingTop: inset.top }]}>\n      <TouchableOpacity\n        style={styles.leftContainer}\n        onPress={() => navigation.goBack()}\n      >\n        <Image\n          source={require(\"../../assets/images/header/sm-solid-caret-right.png\")}\n        />\n      </TouchableOpacity>\n      <TouchableOpacity style={styles.middleContainer} onPress={onTitlePress}>\n        <View style={{ flexDirection: \"row\" }}>\n          <Text\n            style={{ ...h4, flex: 1, textAlign: \"center\" }}\n            numberOfLines={1}\n          >\n            {roomTitle}\n          </Text>\n        </View>\n        <View style={{ flexDirection: \"row\" }}>\n          <Text\n            style={{ ...small, flex: 1, textAlign: \"center\" }}\n            numberOfLines={1}\n          >\n            with <Text style={{ ...smallBold }}>{roomSubtitle}</Text>\n          </Text>\n        </View>\n      </TouchableOpacity>\n      <Text style={styles.rightContainer} onPress={onLeavePress}>\n        Leave\n      </Text>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    backgroundColor: colors.primary900,\n    flexDirection: \"row\",\n    flexWrap: \"nowrap\",\n  },\n  leftContainer: {\n    paddingLeft: 25,\n    height: 70,\n    justifyContent: \"center\",\n  },\n  middleContainer: {\n    paddingHorizontal: 20,\n    flexGrow: 1,\n  },\n  rightContainer: {\n    alignSelf: \"center\",\n    marginRight: 20,\n    ...paragraphBold,\n    color: colors.accent,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/header/SearchHeader.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, TextInput, View } from \"react-native\";\nimport { colors, paragraph, radius } from \"../../constants/dogeStyle\";\nimport { HeaderBase } from \"./HeaderBase\";\n\ntype SearchHeaderProps = {\n  text: string;\n  autoFocus?: boolean;\n  placeHolder?: string;\n  onTextChange: (query: string) => void;\n};\n\nexport const SearchHeader: React.FC<SearchHeaderProps> = ({\n  text,\n  autoFocus = true,\n  placeHolder = \"Search\",\n  onTextChange,\n}) => {\n  return (\n    <HeaderBase>\n      <View style={styles.inputContainer}>\n        <TextInput\n          style={styles.text}\n          placeholder={placeHolder}\n          placeholderTextColor={colors.primary300}\n          numberOfLines={1}\n          onChangeText={onTextChange}\n          value={text}\n          autoFocus={autoFocus}\n        />\n      </View>\n    </HeaderBase>\n  );\n};\n\nconst styles = StyleSheet.create({\n  inputContainer: {\n    flex: 1,\n  },\n  text: {\n    marginLeft: 20,\n    marginRight: 25,\n    height: 40,\n    backgroundColor: colors.primary700,\n    paddingHorizontal: 15,\n    borderRadius: radius.m,\n    ...paragraph,\n    lineHeight: undefined,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/header/TitledHeader.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, Text } from \"react-native\";\nimport { h4 } from \"../../constants/dogeStyle\";\nimport { HeaderBase } from \"./HeaderBase\";\n\ntype TitledHeaderProps = {\n  showBackButton: boolean;\n  title: string;\n};\n\nexport const TitledHeader: React.FC<TitledHeaderProps> = ({\n  showBackButton = true,\n  title,\n}) => {\n  return (\n    <HeaderBase showBackButton={showBackButton}>\n      <Text style={styles.text}>{title}</Text>\n    </HeaderBase>\n  );\n};\n\nconst styles = StyleSheet.create({\n  text: {\n    flex: 1,\n    textAlign: \"center\",\n    alignItems: \"center\",\n    flexDirection: \"row\",\n    marginRight: 66,\n    marginLeft: 25,\n    ...h4,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/minimizedRoomCard/BoxedIcon.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleProp,\n  StyleSheet,\n  TouchableOpacity,\n  ViewStyle,\n} from \"react-native\";\nimport { colors, radius } from \"../../constants/dogeStyle\";\n\ninterface BoxedIconProps {\n  style?: StyleProp<ViewStyle>;\n  image: ImageSourcePropType;\n  imageColor?: string;\n  onPress: () => void;\n}\n\nexport const BoxedIcon: React.FC<BoxedIconProps> = ({\n  style,\n  image,\n  imageColor = colors.text,\n  onPress,\n}) => {\n  return (\n    <TouchableOpacity style={[styles.container, style]} onPress={onPress}>\n      <Image source={image} style={{ tintColor: imageColor }} />\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    width: 40,\n    height: 40,\n    backgroundColor: colors.primary700,\n    borderRadius: radius.m,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/minimizedRoomCard/MinimizedRoomCard.tsx",
    "content": "import React from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleProp,\n  StyleSheet,\n  Text,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { TouchableOpacity } from \"react-native-gesture-handler\";\nimport { colors, paragraph, radius } from \"../../constants/dogeStyle\";\nimport { MultipleUserAvatar } from \"../avatars/MultipleUserAvatar\";\nimport { BoxedIcon } from \"./BoxedIcon\";\n\ninterface MinimizedRoomCardProps {\n  style?: StyleProp<ViewStyle>;\n  onPress: () => void;\n  room: {\n    name: string;\n    speakerAvatars: ImageSourcePropType[];\n    speakers?: string[];\n    roomStartedAt: Date;\n    myself: {\n      isSpeaker?: boolean;\n      isMuted: boolean;\n      isDeafened?: boolean;\n      switchMuted(): void;\n      switchDeafened?: () => void;\n      leave?: () => void;\n    };\n  };\n}\n\nexport const MinimizedRoomCard: React.FC<MinimizedRoomCardProps> = ({\n  style,\n  room,\n  onPress,\n}) => {\n  return (\n    <View style={[styles.container, style]}>\n      <TouchableOpacity\n        style={styles.roomInfo}\n        containerStyle={{ flex: 1 }}\n        onPress={onPress}\n      >\n        {room.speakerAvatars.length > 0 && (\n          <MultipleUserAvatar size=\"xs\" srcArray={room.speakerAvatars} />\n        )}\n        <Text\n          style={{ ...paragraph, marginHorizontal: 10, flex: 1 }}\n          numberOfLines={1}\n        >\n          {room.name}\n        </Text>\n      </TouchableOpacity>\n      <BoxedIcon\n        image={\n          room.myself.isMuted\n            ? require(\"../../assets/images/SolidMicrophoneOff.png\")\n            : require(\"../../assets/images/bxs-microphone.png\")\n        }\n        imageColor={colors.text}\n        onPress={() => room.myself.switchMuted()}\n        style={{ width: 60, margin: 10 }}\n      />\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    alignItems: \"center\",\n    backgroundColor: colors.primary800,\n    borderColor: colors.accent,\n    borderRadius: radius.m,\n    borderWidth: 1,\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    shadowColor: colors.accent,\n    shadowOpacity: 0.15,\n    shadowRadius: 20,\n    width: 295,\n  },\n  roomInfo: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    padding: 10,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/notifications/FollowNotification.tsx",
    "content": "import React from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  fontFamily,\n  fontSize,\n  paragraph,\n  radius,\n} from \"../../constants/dogeStyle\";\nimport { SingleUserAvatar } from \"../avatars/SingleUserAvatar\";\nimport { GenericNotification } from \"./GenericNotification\";\n\ninterface FollowNotificationProps {\n  style?: ViewStyle;\n  userAvatarSrc: ImageSourcePropType;\n  username: string;\n  userProfileLink?: string;\n  time: string;\n  isOnline?: boolean;\n  following?: boolean;\n}\n\nexport const FollowNotification: React.FC<FollowNotificationProps> = ({\n  style,\n  userAvatarSrc,\n  isOnline = false,\n  username,\n  userProfileLink,\n  time,\n  following = false,\n}) => {\n  const icon = (\n    <SingleUserAvatar src={userAvatarSrc} size=\"sm\" isOnline={isOnline} />\n  );\n\n  const notificationMsg = (\n    <View style={{ flexDirection: \"row\" }}>\n      <Text style={[styles.title, { fontWeight: \"700\" }]}>\n        {username}\n        <Text style={styles.title}> followed you</Text>\n      </Text>\n    </View>\n  );\n\n  const followButton = (\n    <TouchableOpacity\n      style={[\n        styles.button,\n        following && { backgroundColor: colors.primary700 },\n      ]}\n    >\n      <Text style={styles.buttonTitle}>\n        {following ? \"Following\" : \"Follow back\"}\n      </Text>\n    </TouchableOpacity>\n  );\n\n  return (\n    <GenericNotification\n      style={style}\n      notificationMsg={notificationMsg}\n      time={time}\n      icon={icon}\n      actionButton={followButton}\n    />\n  );\n};\n\nconst styles = StyleSheet.create({\n  title: {\n    ...paragraph,\n    flex: 1,\n    flexWrap: \"wrap\",\n    lineHeight: 18,\n  },\n  button: {\n    height: 22,\n    width: 90,\n    backgroundColor: colors.accent,\n    borderRadius: radius.s,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonTitle: {\n    fontSize: fontSize.small,\n    fontFamily: fontFamily.semiBold,\n    fontWeight: \"700\",\n    color: colors.text,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/notifications/GenericNotification.tsx",
    "content": "import React, { ReactNode } from \"react\";\nimport { StyleSheet, Text, View, ViewStyle } from \"react-native\";\nimport Icon from \"react-native-vector-icons/Ionicons\";\nimport { colors, paragraph, small } from \"../../constants/dogeStyle\";\n\ninterface GenericNotificationProps {\n  style?: ViewStyle;\n  notificationMsg: ReactNode;\n  time: string;\n  actionButton?: ReactNode;\n  icon?: ReactNode;\n}\n\nexport const GenericNotification: React.FC<GenericNotificationProps> = ({\n  style,\n  notificationMsg,\n  time,\n  actionButton,\n  icon,\n}) => {\n  const defaultMessage = (\n    <View style={{ flexDirection: \"row\" }}>\n      <Text style={styles.title}>{\"You have a notification\"}</Text>\n    </View>\n  );\n  return (\n    <View style={[style, styles.container]}>\n      <View>\n        {icon ? icon : <Icon name={\"rocket\"} size={32} color={colors.text} />}\n      </View>\n      <View style={styles.middleView}>\n        {notificationMsg ? notificationMsg : defaultMessage}\n        <Text style={styles.time}>{time}</Text>\n      </View>\n      {actionButton && <View style={styles.button}>{actionButton}</View>}\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n  },\n  middleView: {\n    marginHorizontal: 16,\n    flexGrow: 1,\n  },\n  title: {\n    ...paragraph,\n    lineHeight: 18,\n    flex: 1,\n    flexWrap: \"wrap\",\n  },\n  time: {\n    ...small,\n    color: colors.primary300,\n    lineHeight: 18,\n  },\n  button: {\n    height: 22,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/notifications/LiveNotification.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  fontFamily,\n  fontSize,\n  paragraph,\n  radius,\n} from \"../../constants/dogeStyle\";\nimport { GenericNotification } from \"./GenericNotification\";\n\ninterface LiveNotificationProps {\n  style?: ViewStyle;\n  username: string;\n  userProfileLink?: string;\n  time: string;\n  joined?: boolean;\n}\n\nexport const LiveNotification: React.FC<LiveNotificationProps> = ({\n  style,\n  username,\n  userProfileLink,\n  time,\n  joined = false,\n}) => {\n  const icon = (\n    <Image source={require(\"../../assets/images/lg-solid-time.png\")} />\n  );\n\n  const notificationMsg = (\n    <View style={{ flexDirection: \"row\" }}>\n      <Text style={[styles.title, { fontWeight: \"700\" }]}>\n        {username}\n        <Text style={styles.title}> is now live!</Text>\n      </Text>\n    </View>\n  );\n\n  const joinButton = (\n    <TouchableOpacity style={styles.button}>\n      <Text style={styles.buttonTitle}>{joined ? \"Joined\" : \"Join room\"}</Text>\n    </TouchableOpacity>\n  );\n\n  return (\n    <GenericNotification\n      style={style}\n      notificationMsg={notificationMsg}\n      time={time}\n      icon={icon}\n      actionButton={joinButton}\n    />\n  );\n};\n\nconst styles = StyleSheet.create({\n  title: {\n    ...paragraph,\n    lineHeight: 18,\n    flex: 1,\n    flexWrap: \"wrap\",\n  },\n  button: {\n    height: 22,\n    width: 90,\n    backgroundColor: colors.accent,\n    borderRadius: radius.s,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonTitle: {\n    fontSize: fontSize.small,\n    fontFamily: fontFamily.semiBold,\n    fontWeight: \"700\",\n    color: colors.text,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/notifications/NewRoomNotification.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  fontFamily,\n  fontSize,\n  paragraph,\n  radius,\n} from \"../../constants/dogeStyle\";\nimport { GenericNotification } from \"./GenericNotification\";\n\ninterface NewRoomNotificationProps {\n  style?: ViewStyle;\n  username: string;\n  userProfileLink?: string;\n  time: string;\n  joined?: boolean;\n}\n\nexport const NewRoomNotification: React.FC<NewRoomNotificationProps> = ({\n  style,\n  username,\n  userProfileLink,\n  time,\n  joined = false,\n}) => {\n  const icon = <Image source={require(\"../../assets/images/ios-rocket.png\")} />;\n\n  const notificationMsg = (\n    <View style={{ flexDirection: \"row\" }}>\n      <Text style={[styles.title, { fontWeight: \"700\" }]}>\n        {username}\n        <Text style={styles.title}> created a room</Text>\n      </Text>\n    </View>\n  );\n\n  const joinButton = (\n    <TouchableOpacity style={styles.button}>\n      <Text style={styles.buttonTitle}>{joined ? \"Joined\" : \"Join room\"}</Text>\n    </TouchableOpacity>\n  );\n\n  return (\n    <GenericNotification\n      style={style}\n      notificationMsg={notificationMsg}\n      time={time}\n      icon={icon}\n      actionButton={joinButton}\n    />\n  );\n};\n\nconst styles = StyleSheet.create({\n  title: {\n    ...paragraph,\n    lineHeight: 18,\n    flex: 1,\n    flexWrap: \"wrap\",\n  },\n  button: {\n    height: 22,\n    width: 90,\n    backgroundColor: colors.accent,\n    borderRadius: radius.s,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonTitle: {\n    fontSize: fontSize.small,\n    fontFamily: fontFamily.semiBold,\n    fontWeight: \"700\",\n    color: colors.text,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/report/Report.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { Animated, StyleSheet, Text, View, ViewProps } from \"react-native\";\nimport { h4, paragraph, paragraphBold } from \"../../constants/dogeStyle\";\n\nexport type ReportProps = ViewProps & {\n  user: User;\n};\n\nconst ReasonPicker: React.FC<ReportProps> = ({ style, user }) => {\n  return (\n    <View>\n      <Text style={{ ...h4 }}>Report {user.username}</Text>\n      <Text style={{ ...paragraph }}>\n        We are going to ask you a couple of questions to assist us with\n        investigating your report rapidly and precisely.\n      </Text>\n      <Text style={{ ...paragraphBold }}>What would you like to report?</Text>\n    </View>\n  );\n};\n\nexport const Report: React.FC<ReportProps> = ({ style, user }) => {\n  let spinValue = new Animated.Value(0);\n\n  return <View></View>;\n};\n\nconst styles = StyleSheet.create({});\n"
  },
  {
    "path": "pilaf/src/components/search/RoomSearchResult.tsx",
    "content": "import { differenceInMilliseconds, format, isPast, isToday } from \"date-fns\";\nimport React, { useEffect, useState } from \"react\";\nimport {\n  Image,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewProps,\n  ViewStyle,\n} from \"react-native\";\nimport {\n  colors,\n  paragraphBold,\n  radius,\n  small,\n} from \"../../constants/dogeStyle\";\nimport { BubbleText } from \"../BubbleText\";\nimport { RoomCardHeading } from \"../RoomCardHeading\";\n\nfunction formatNumber(num: number): string {\n  return Math.abs(num) > 999\n    ? `${Math.sign(num) * Number((Math.abs(num) / 1000).toFixed(1))}K`\n    : `${Math.sign(num) * Math.abs(num)}`;\n}\n\nfunction useScheduleRerender(scheduledFor?: Date) {\n  const [, rerender] = useState(0);\n\n  useEffect(() => {\n    if (!scheduledFor) {\n      return;\n    }\n\n    let done = false;\n    const id = setTimeout(() => {\n      done = true;\n      rerender((x) => x + 1);\n    }, differenceInMilliseconds(scheduledFor, new Date()) + 1000);\n\n    return () => {\n      if (!done) {\n        clearTimeout(id);\n      }\n    };\n  }, [scheduledFor]);\n}\n\nexport type RoomSearchResultProps = ViewProps & {\n  style?: ViewStyle;\n  title: string;\n  subtitle: string;\n  scheduledFor?: Date;\n  listeners: number;\n  onPress?: () => void;\n};\n\nexport const RoomSearchResult: React.FC<RoomSearchResultProps> = ({\n  style,\n  title,\n  subtitle,\n  scheduledFor,\n  listeners,\n  onPress,\n}) => {\n  useScheduleRerender(scheduledFor);\n\n  let scheduledForLabel = \"\";\n\n  if (scheduledFor) {\n    if (isToday(scheduledFor)) {\n      scheduledForLabel = format(scheduledFor, \"K:mm a\");\n    } else {\n      scheduledForLabel = format(scheduledFor, \"LLL d\");\n    }\n  }\n\n  const roomLive = !scheduledFor || isPast(scheduledFor);\n\n  return (\n    <TouchableOpacity style={[styles.container, style]} onPress={onPress}>\n      <View style={styles.topContainer}>\n        <View style={styles.topLeftContainer}>\n          <RoomCardHeading\n            icon={\n              roomLive ? undefined : (\n                <Image\n                  source={require(\"../../assets/images/lg-solid-time.png\")}\n                />\n              )\n            }\n            text={title}\n          />\n          <View style={styles.subtitleContainer}>\n            <Text style={styles.subtitle} numberOfLines={1}>\n              {subtitle}\n            </Text>\n          </View>\n        </View>\n        <View style={styles.topRightContainer}>\n          <BubbleText style={{ alignSelf: \"flex-start\" }} live={roomLive}>\n            <Text style={styles.bubbleLabel}>\n              {roomLive ? formatNumber(listeners) : scheduledForLabel}\n            </Text>\n          </BubbleText>\n        </View>\n      </View>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    borderRadius: radius.m,\n    paddingVertical: 10,\n  },\n  topContainer: {\n    flexDirection: \"row\",\n  },\n  topLeftContainer: {\n    flex: 1,\n  },\n  topRightContainer: {},\n  headingContainer: {\n    flexDirection: \"row\",\n  },\n  subtitleContainer: {\n    flexDirection: \"row\",\n  },\n  subtitle: {\n    ...small,\n    color: colors.primary300,\n    flex: 1,\n  },\n  bubbleLabel: {\n    ...paragraphBold,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/search/SearchHistoryResult.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport { colors, paragraph, radius } from \"../../constants/dogeStyle\";\n\nexport type SearchHistoryResultProps = ViewProps & {\n  query: string;\n  onPress?: () => void;\n  onDeletePress?: () => void;\n};\n\nexport const SearchHistoryResult: React.FC<SearchHistoryResultProps> = ({\n  style,\n  query,\n  onDeletePress,\n  onPress,\n}) => {\n  return (\n    <TouchableOpacity style={[styles.container, style]} onPress={onPress}>\n      <Image\n        source={require(\"../../assets/images/header/sm-solid-search.png\")}\n        style={{ tintColor: colors.primary300, height: 18, width: 18 }}\n      />\n      <View style={styles.textContainer}>\n        <Text style={styles.text} numberOfLines={1}>\n          {query}\n        </Text>\n      </View>\n      <TouchableOpacity style={styles.button} onPress={onDeletePress}>\n        <Text style={styles.buttonText}>Delete</Text>\n      </TouchableOpacity>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    borderRadius: radius.m,\n    paddingVertical: 10,\n  },\n  textContainer: { marginHorizontal: 24, flex: 1 },\n  text: {\n    ...paragraph,\n    color: colors.primary300,\n  },\n  button: {\n    alignSelf: \"flex-end\",\n  },\n  buttonText: {\n    ...paragraph,\n    color: colors.accent,\n    textDecorationLine: \"underline\",\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/search/SearchHistoryResultList.tsx",
    "content": "import React from \"react\";\nimport { ScrollView, StyleSheet, Text, View, ViewProps } from \"react-native\";\nimport { colors, h4, radius } from \"../../constants/dogeStyle\";\n\nexport const SearchHistoryResultList: React.FC<ViewProps> = ({\n  children,\n  style,\n}) => {\n  return (\n    <View style={[styles.container, style]}>\n      <Text style={{ ...h4 }}>Recent</Text>\n      <ScrollView style={{ marginTop: 10 }}>{children}</ScrollView>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: colors.primary900,\n    borderRadius: radius.m,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/components/search/SearchResultList.tsx",
    "content": ""
  },
  {
    "path": "pilaf/src/components/search/UserSearchResult.tsx",
    "content": "import React from \"react\";\nimport {\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport {\n  colors,\n  paragraphBold,\n  radius,\n  small,\n} from \"../../constants/dogeStyle\";\nimport { SingleUserAvatar } from \"../avatars/SingleUserAvatar\";\n\nexport type UserSearchResultProps = ViewProps & {\n  userAvatarSrc: ImageSourcePropType;\n  userName: string;\n  userLink: string;\n  isOnline: boolean;\n  onPress?: () => void;\n};\n\nexport const UserSearchResult: React.FC<UserSearchResultProps> = ({\n  style,\n  userAvatarSrc,\n  userName,\n  userLink,\n  isOnline,\n  onPress,\n}) => {\n  return (\n    <TouchableOpacity style={[styles.container, style]} onPress={onPress}>\n      <SingleUserAvatar src={userAvatarSrc} size=\"sm\" isOnline={isOnline} />\n      <View style={styles.textContainer}>\n        <Text style={styles.title}>{userName}</Text>\n        <Text style={styles.subtitle}>{userLink}</Text>\n      </View>\n    </TouchableOpacity>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: \"row\",\n    borderRadius: radius.m,\n    paddingVertical: 10,\n    alignItems: \"center\",\n  },\n  textContainer: { marginLeft: 12 },\n  title: {\n    ...paragraphBold,\n    //lineHeight: 20,\n  },\n  subtitle: {\n    ...small,\n    color: colors.primary300,\n    //lineHeight: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/constants/dogeStyle.ts",
    "content": "import { TextStyle } from \"react-native\";\n\nexport const radius = {\n  s: 5,\n  m: 8,\n  l: 16,\n};\n\nexport const fontFamily = {\n  black: \"Inter-Black\",\n  bold: \"Inter-Bold\",\n  extraBold: \"Inter-ExtraBold\",\n  extraLight: \"Inter-ExtraLight\",\n  light: \"Inter-Light\",\n  medium: \"Inter-Medium\",\n  regular: \"Inter-Regular\",\n  semiBold: \"Inter-SemiBold\",\n  thin: \"Inter-Thin\",\n};\n\nexport const fontSize = {\n  h1: 56,\n  h2: 40,\n  h3: 28,\n  h4: 20,\n  paragraph: 14,\n  small: 12,\n  xs: 10,\n};\n\nexport const colors = {\n  text: \"#fff\",\n  primary100: \"#dee3ea\",\n  primary200: \"#b2bdcd\",\n  primary300: \"#5d7290\",\n  primary600: \"#323d4d\",\n  primary700: \"#242c37\",\n  primary800: \"#151a21\",\n  primary900: \"#0b0e11\",\n  secondary: \"#5575e7\",\n  secondaryWashedOut: \"#879eed\",\n  accent: \"#fd4d4d\",\n  accentHover: \"#fd6868\",\n  accentDisabled: \"#f5bfbf\",\n  black: \"#000\",\n};\n\nconst textBase: TextStyle = {\n  fontFamily: fontFamily.regular,\n  color: colors.text,\n};\n\nexport const h1: TextStyle = {\n  ...textBase,\n  lineHeight: 90,\n  fontSize: fontSize.h1,\n  fontWeight: \"700\",\n};\n\nexport const h2: TextStyle = {\n  ...textBase,\n  lineHeight: 64,\n  fontSize: fontSize.h2,\n  fontWeight: \"700\",\n};\n\nexport const h3: TextStyle = {\n  ...textBase,\n  lineHeight: 45,\n  fontSize: fontSize.h3,\n  fontWeight: \"700\",\n};\n\nexport const h4: TextStyle = {\n  ...textBase,\n  lineHeight: 32,\n  fontSize: fontSize.h4,\n  fontWeight: \"700\",\n};\n\nexport const paragraph: TextStyle = {\n  ...textBase,\n  fontWeight: \"500\",\n  fontSize: fontSize.paragraph,\n  lineHeight: 22,\n};\n\nexport const paragraphBold: TextStyle = {\n  ...paragraph,\n  fontWeight: \"700\",\n};\n\nexport const small: TextStyle = {\n  ...textBase,\n  fontWeight: \"500\",\n  fontSize: fontSize.small,\n  lineHeight: 22,\n};\n\nexport const smallBold: TextStyle = {\n  ...small,\n  fontWeight: \"700\",\n};\n\nexport const xsmall: TextStyle = {\n  ...textBase,\n  fontWeight: \"500\",\n  fontSize: fontSize.xs,\n  lineHeight: 16,\n};\n\nexport const xsmallBold: TextStyle = {\n  ...xsmall,\n  fontWeight: \"700\",\n};\n"
  },
  {
    "path": "pilaf/src/constants/env.ts",
    "content": "export const apiBaseUrl = \"https://api.dogehouse.tv\";\n"
  },
  {
    "path": "pilaf/src/constants/regex.ts",
    "content": "export const linkRegex = /(https?:\\/\\/)(www\\.)?([-a-z0-9]{1,63}\\.)*?[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\\.[a-z]{1,6}(\\/[-\\\\w@\\\\+\\\\.~#\\\\?&/=%]*)?[^\\s()]+/;\nexport const codeBlockRegex = /`([^`]*)`/g;\n"
  },
  {
    "path": "pilaf/src/global-stores/useCurrentRoomIdStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype Fn = (currentRoomId: string | null) => string | null;\n\nexport const useCurrentRoomIdStore = create(\n  combine(\n    {\n      currentRoomId: null as string | null,\n    },\n    (set) => ({\n      set,\n      setCurrentRoomId: (currentRoomIdOrFn: string | null | Fn) => {\n        if (typeof currentRoomIdOrFn === \"function\") {\n          set((s) => ({ currentRoomId: currentRoomIdOrFn(s.currentRoomId) }));\n        } else {\n          set({ currentRoomId: currentRoomIdOrFn });\n        }\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/global-stores/useMicPermErrorStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useMicPermErrorStore = create(\n  combine(\n    {\n      error: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/global-stores/useMuteStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../modules/sound-effect/useSoundEffectStore\";\n\nexport const useMuteStore = create(\n  combine(\n    {\n      muted: false,\n    },\n    (set) => ({\n      // don't call this directly unless you know what you are doing\n      // use useSetMute hook intead\n      setInternalMute: (muted: boolean) => {\n        if (muted) {\n          useSoundEffectStore.getState().playSoundEffect(\"mute\");\n        } else {\n          useSoundEffectStore.getState().playSoundEffect(\"unmute\");\n        }\n        set({ muted });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/global-stores/useProducerStore.ts",
    "content": "import { Producer } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useProducerStore = create(\n  combine(\n    {\n      producer: null as Producer | null,\n    },\n    (set) => ({\n      add: (p: Producer) =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n\n          return { producer: p };\n        }),\n      close: () =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n\n          return {\n            producer: null,\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/global-stores/useRoomChatMentionStore.ts",
    "content": "import { BaseUser } from \"@dogehouse/kebab\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../modules/sound-effect/useSoundEffectStore\";\n\nexport const useRoomChatMentionStore = create(\n  combine(\n    {\n      mentions: [] as BaseUser[],\n      queriedUsernames: [] as BaseUser[],\n      activeUsername: \"\",\n      iAmMentioned: 0,\n    },\n    (set) => ({\n      setMentions: (mentions: BaseUser[]) =>\n        set({\n          mentions,\n        }),\n      setQueriedUsernames: (queriedUsernames: BaseUser[]) =>\n        set({\n          queriedUsernames,\n        }),\n      setActiveUsername: (activeUsername: string) => {\n        return set({\n          activeUsername,\n        });\n      },\n      resetIAmMentioned: () =>\n        set({\n          iAmMentioned: 0,\n        }),\n      incrementIAmMentioned: () => {\n        useSoundEffectStore.getState().playSoundEffect(\"room_chat_mention\");\n        set((x) => ({ iAmMentioned: x.iAmMentioned + 1 }));\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/lib/createChatMessage.ts",
    "content": "import { BaseUser } from \"@dogehouse/kebab\";\nimport { codeBlockRegex, linkRegex } from \"../constants/regex\";\n\nexport const createChatMessage = (\n  message: string,\n  mentions: BaseUser[],\n  roomUsers: BaseUser[] = []\n) => {\n  const tokens = ([] as unknown) as [\n    {\n      t: string;\n      v: string;\n    }\n  ];\n\n  const whisperedToUsernames: string[] = [];\n\n  const testAndPushToken = (item: string) => {\n    const isLink = linkRegex.test(item);\n    const withoutAt = item.replace(/@|#/g, \"\");\n    const isMention = mentions.find((m) => withoutAt === m.username);\n    // whisperedTo users list\n    if (isMention && item.startsWith(\"#@\")) {\n      whisperedToUsernames.push(withoutAt);\n    }\n\n    if (isLink || isMention) {\n      tokens.push({\n        t: isLink ? \"link\" : \"mention\",\n        v: isMention ? withoutAt : item,\n      });\n    } else if (item.startsWith(\":\") && item.endsWith(\":\") && item.length > 2) {\n      tokens.push({\n        t: \"emote\",\n        v: item.slice(1, item.length - 1),\n      });\n    } else {\n      tokens.push({\n        t: \"text\",\n        v: item,\n      });\n    }\n  };\n\n  // const match = message.matchAll(new RegExp(codeBlockRegex, \"g\"));\n  const regex = new RegExp(codeBlockRegex, \"g\");\n  let match;\n  let matchResult = [];\n  while ((match = regex.exec(message)) !== null) {\n    matchResult.push(match[0]);\n  }\n  // let matchResult = match.next();\n\n  // For message that matches the regex pattern of code blocks.\n  if (matchResult.length) {\n    const splitMessage = message.split(codeBlockRegex);\n\n    splitMessage.forEach((text, index) => {\n      // First and last index is empty string while split using the code block regex.\n      if (!index && index === splitMessage.length - 1) {\n        return;\n      }\n\n      const trimmed = text.trim();\n\n      if (matchResult.length && text === matchResult[1]) {\n        if (trimmed) {\n          tokens.push({\n            t: \"block\",\n            v: trimmed,\n          });\n        } else {\n          tokens.push({\n            t: \"text\",\n            v: matchResult[0],\n          });\n        }\n\n        matchResult = match.next();\n      } else {\n        text.split(\" \").forEach((item) => {\n          testAndPushToken(item);\n        });\n      }\n    });\n  } else {\n    message.split(\" \").forEach((item) => {\n      testAndPushToken(item);\n    });\n  }\n\n  return {\n    tokens,\n    whisperedTo: roomUsers\n      .filter((u) =>\n        whisperedToUsernames\n          .map((x) => x?.toLowerCase())\n          .includes(u.username?.toLowerCase())\n      )\n      .map((u) => u.id),\n  };\n};\n"
  },
  {
    "path": "pilaf/src/lib/inCallManagerCenter.ts",
    "content": "import InCallManager from \"react-native-incall-manager\";\n\nexport const InCallManagerStart = () => {\n  if (InCallManager.recordPermission !== \"granted\") {\n    InCallManager.requestRecordPermission()\n      .then((requestedRecordPermissionResult) => {\n        if (requestedRecordPermissionResult === \"granted\") {\n          InCallManager.start({ media: \"audio\" });\n        }\n      })\n      .catch((err) => {\n        console.log(\"InCallManager.requestRecordPermission() catch: \", err);\n      });\n  }\n};\n\nexport const InCallManagerSetSpeakerOn = (on: boolean) => {\n  InCallManager.setSpeakerphoneOn(on);\n};\n"
  },
  {
    "path": "pilaf/src/lib/notificationCenter.ts",
    "content": "import PushNotificationIOS from \"@react-native-community/push-notification-ios\";\nimport { Platform } from \"react-native\";\nimport PushNotification, {\n  PushNotificationObject,\n  PushNotificationPermissions,\n} from \"react-native-push-notification\";\nimport * as RootNavigation from \"../navigation/RootNavigation\";\n\nexport const configureNotificationCenter = () => {\n  // Must be outside of any component LifeCycle (such as `componentDidMount`).\n  PushNotification.configure({\n    // (optional) Called when Token is generated (iOS and Android)\n    onRegister: function (token) {\n      console.log(\"TOKEN:\", token);\n    },\n\n    // (required) Called when a remote is received or opened, or local notification is opened\n    onNotification: function (notification) {\n      console.log(\"NOTIFICATION:\", notification);\n      console.log(\"ACTION:\", notification.action);\n\n      if (notification.action === \"join\") {\n        if (notification.data.locale) {\n          RootNavigation.navigate(\"Room\", { roomId: notification.data.roomId });\n        }\n      }\n\n      // process the notification\n\n      // (required) Called when a remote is received or opened, or local notification is opened\n      notification.finish(PushNotificationIOS.FetchResult.NoData);\n    },\n\n    // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)\n    onAction: function (notification) {\n      console.log(\"ACTION:\", notification.action);\n      console.log(\"NOTIFICATION:\", notification);\n\n      // process the action\n    },\n\n    // (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS)\n    onRegistrationError: function (err) {\n      console.error(err.message, err);\n    },\n\n    // IOS ONLY (optional): default: all - Permissions to register.\n    permissions: {\n      alert: true,\n      badge: true,\n      sound: true,\n    },\n\n    // Should the initial notification be popped automatically\n    // default: true\n    popInitialNotification: true,\n\n    /**\n     * (optional) default: true\n     * - Specified if permissions (ios) and token (android and ios) will requested or not,\n     * - if not, you must call PushNotificationsHandler.requestPermissions() later\n     * - if you are not using remote notification or do not have Firebase installed, use this:\n     *     requestPermissions: Platform.OS === 'ios'\n     */\n    requestPermissions: false,\n  });\n\n  if (Platform.OS === \"ios\") {\n    PushNotificationIOS.setNotificationCategories([\n      {\n        id: \"room_created\",\n        actions: [\n          { id: \"join\", title: \"Join the room\", options: { foreground: true } },\n          {\n            id: \"ignore\",\n            title: \"Ignore\",\n            options: { foreground: true, destructive: true },\n          },\n        ],\n      },\n    ]);\n  }\n};\n\nconst pushPermissionSafe = (notification: PushNotificationObject) => {\n  PushNotification.checkPermissions(\n    (permission: PushNotificationPermissions) => {\n      if (permission.alert) {\n        PushNotification.localNotification(notification);\n      } else {\n        PushNotification.requestPermissions()\n          .then((value: PushNotificationPermissions) => {\n            PushNotification.localNotification(notification);\n          })\n          .catch((reason: any) => {});\n      }\n    }\n  );\n};\n\nexport const pushRoomCreateNotification = (\n  username: string,\n  roomName: string,\n  roomId: string\n) => {\n  pushPermissionSafe({\n    id: 0,\n    title: username + \" created a room\",\n    message: roomName,\n    userInfo: { roomId: roomId, locale: true },\n    playSound: false,\n    soundName: \"default\",\n    category: \"room_created\",\n  });\n};\n\nexport const pushRoomInvitationNotification = (\n  username: string,\n  roomName: string,\n  roomId: string\n) => {\n  pushPermissionSafe({\n    id: 0,\n    title: username + \" invites you\",\n    message: \"Room: \" + roomName,\n    userInfo: { roomId: roomId, locale: true },\n    playSound: false,\n    soundName: \"default\",\n    category: \"room_created\",\n  });\n};\n"
  },
  {
    "path": "pilaf/src/lib/queryClient.ts",
    "content": "import { QueryClient } from \"react-query\";\nimport { apiBaseUrl } from \"../constants/env\";\nimport { useTokenStore } from \"../modules/auth/useTokenStore\";\n\nexport const defaultQueryFn = async ({ queryKey }: { queryKey: string }) => {\n  const { accessToken, refreshToken } = useTokenStore.getState();\n  const r = await fetch(`${apiBaseUrl}${queryKey[0]}`, {\n    headers: {\n      \"X-Access-Token\": accessToken,\n      \"X-Refresh-Token\": refreshToken,\n    },\n  });\n\n  if (r.status !== 200) {\n    throw new Error(await r.text());\n  }\n  const _accessToken = r.headers.get(\"access-token\");\n  const _refreshToken = r.headers.get(\"refresh-token\");\n\n  if (_accessToken && _refreshToken) {\n    useTokenStore.getState().setTokens({\n      accessToken: _accessToken,\n      refreshToken: _refreshToken,\n    });\n  }\n\n  return await r.json();\n};\n\nexport const queryClient = new QueryClient({\n  defaultOptions: {\n    mutations: {\n      onError: (e) => {\n        if (\"message\" in (e as Error)) {\n          //showErrorToast((e as Error).message);\n          console.log((e as Error).message);\n        }\n      },\n    },\n    queries: {\n      retry: false,\n      staleTime: 60 * 1000 * 5,\n      onError: (e) => {\n        if (\"message\" in (e as Error)) {\n          console.log((e as Error).message);\n        }\n      },\n      queryFn: defaultQueryFn,\n    },\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/auth/WaitForWsAndAuth.tsx",
    "content": "import React, { useContext } from \"react\";\nimport { ActivityIndicator, Text, View } from \"react-native\";\nimport { colors, h4 } from \"../../constants/dogeStyle\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { useVerifyLoggedIn } from \"./useVerifyLoggedIn\";\n\ninterface WaitForWsAndAuthProps {}\n\nexport const WaitForWsAndAuth: React.FC<WaitForWsAndAuthProps> = ({\n  children,\n}) => {\n  const { conn } = useContext(WebSocketContext);\n\n  if (!useVerifyLoggedIn()) {\n    // This should never happens\n    return null;\n  }\n\n  if (!conn) {\n    // @todo make this better\n    return (\n      <View\n        style={{\n          flex: 1,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          backgroundColor: colors.primary900,\n        }}\n      >\n        <ActivityIndicator />\n        <Text style={{ marginTop: 20, ...h4 }}>Going to the moon...</Text>\n      </View>\n    );\n  }\n\n  return <>{children}</>;\n};\n"
  },
  {
    "path": "pilaf/src/modules/auth/useSaveTokensFromQueryParams.ts",
    "content": "import { Linking } from \"react-native\";\nimport { InAppBrowser } from \"react-native-inappbrowser-reborn\";\nimport { useTokenStore } from \"./useTokenStore\";\n\nfunction getUrlParameter(url: string, name: string) {\n  name = name.replace(/[\\[]/, \"\\\\[\").replace(/[\\]]/, \"\\\\]\");\n  var regex = new RegExp(\"[\\\\?&]\" + name + \"=([^&#]*)\");\n  var results = regex.exec(url);\n  return results === null\n    ? \"\"\n    : decodeURIComponent(results[1].replace(/\\+/g, \" \"));\n}\n\nexport const useSaveTokensFromQueryParams = () => {\n  Linking.addEventListener(\"url\", async (e) => {\n    const accessToken = getUrlParameter(e.url, \"accessToken\");\n    const refreshToken = getUrlParameter(e.url, \"refreshToken\");\n    await useTokenStore.getState().setTokens({\n      accessToken: accessToken,\n      refreshToken: refreshToken,\n    });\n    InAppBrowser.close();\n  });\n};\n"
  },
  {
    "path": "pilaf/src/modules/auth/useTokenStore.ts",
    "content": "// TODO Token should not be store in unsecure AsyncStorage\nimport AsyncStorage from \"@react-native-async-storage/async-storage\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { accessToken, refreshToken } from \"../../../tokens\";\nconst accessTokenKey = \"@toum/token\";\nconst refreshTokenKey = \"@toum/refresh-token\";\n\nexport const useTokenStore = create(\n  combine(\n    {\n      accessToken,\n      refreshToken,\n    },\n    (set) => ({\n      setTokens: async (x: { accessToken: string; refreshToken: string }) => {\n        try {\n          await AsyncStorage.setItem(accessTokenKey, x.accessToken);\n          await AsyncStorage.setItem(refreshTokenKey, x.refreshToken);\n        } catch {}\n\n        set(x);\n      },\n      loadTokens: async () => {\n        try {\n          let accessToken = await AsyncStorage.getItem(accessTokenKey);\n          accessToken = accessToken || \"\";\n          let refreshToken = await AsyncStorage.getItem(refreshTokenKey);\n          refreshToken = refreshToken || \"\";\n          set({ accessToken, refreshToken });\n        } catch {}\n      },\n    })\n  )\n);\n// export const useTokenStore = create(\n//   combine(getDefaultValues(), (set) => ({\n//     setTokens: (x: { accessToken: string; refreshToken: string }) => {\n//       try {\n//         localStorage.setItem(accessTokenKey, x.accessToken);\n//         localStorage.setItem(refreshTokenKey, x.refreshToken);\n//       } catch {}\n\n//       set(x);\n//     },\n//   }))\n// );\n"
  },
  {
    "path": "pilaf/src/modules/auth/useVerifyLoggedIn.ts",
    "content": "import { useTokenStore } from \"./useTokenStore\";\n\nexport const useVerifyLoggedIn = () => {\n  const hasTokens = useTokenStore((s) => s.accessToken && s.refreshToken);\n\n  return hasTokens;\n};\n"
  },
  {
    "path": "pilaf/src/modules/explore/ExploreController.tsx",
    "content": "import React from \"react\";\nimport { Text } from \"react-native\";\n\nexport const ExploreController: React.FC = () => {\n  return <Text>Explore page</Text>;\n};\n"
  },
  {
    "path": "pilaf/src/modules/feed/FeedController.tsx",
    "content": "import { Room, ScheduledRoom } from \"@dogehouse/kebab\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport React, { useContext } from \"react\";\nimport { RoomCard } from \"../../components/RoomCard\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useRoomChatStore } from \"../../modules/room/chat/useRoomChatStore\";\nimport { WebSocketContext } from \"../../modules/ws/WebSocketProvider\";\nimport { useTypeSafePrefetch } from \"../../shared-hooks/useTypeSafePrefetch\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\n\ninterface FeedControllerProps {}\n\nexport const FeedController: React.FC<FeedControllerProps> = ({}) => {\n  const { conn } = useContext(WebSocketContext);\n  // @todo pagination\n  const { isLoading, data } = useTypeSafeQuery(\"getTopPublicRooms\", {\n    staleTime: Infinity,\n    enabled: !!conn,\n    refetchOnMount: \"always\",\n    refetchInterval: 10000,\n  });\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const prefetch = useTypeSafePrefetch();\n\n  const navigation = useNavigation();\n  const [clearChat] = useRoomChatStore((s) => [s.clearChat]);\n  if (!conn || isLoading || !data) {\n    return null;\n  }\n\n  return (\n    <>\n      {data.rooms.map((room: Room | ScheduledRoom, index) => (\n        <RoomCard\n          key={index}\n          style={{ marginBottom: 20 }}\n          title={room.name}\n          subtitle={\n            \"peoplePreviewList\" in room\n              ? room.peoplePreviewList\n                  .slice(0, 3)\n                  .map((x) => x.displayName)\n                  .join(\", \")\n              : \"\"\n          }\n          scheduledFor={\n            \"scheduledFor\" in room ? new Date(room.scheduledFor) : undefined\n          }\n          listeners={\"numPeopleInside\" in room ? room.numPeopleInside : 0}\n          tags={[]}\n          avatarSrcs={[]}\n          onPress={() => {\n            if (room.id !== currentRoomId) {\n              clearChat();\n              prefetch([\"joinRoomAndGetInfo\", room.id], [room.id]);\n            }\n            navigation.navigate(\"Room\", { roomId: room.id });\n          }}\n        />\n      ))}\n    </>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/following/FollowersOnline.tsx",
    "content": "import { UserWithFollowInfo } from \"@dogehouse/kebab\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport React, { MouseEventHandler } from \"react\";\nimport {\n  ScrollView,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { SingleUserAvatar } from \"../../components/avatars/SingleUserAvatar\";\nimport { Button } from \"../../components/buttons/Button\";\nimport { colors, h3, paragraphBold, small } from \"../../constants/dogeStyle\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useTypeSafePrefetch } from \"../../shared-hooks/useTypeSafePrefetch\";\nimport { useRoomChatStore } from \"../room/chat/useRoomChatStore\";\n\nexport interface FriendOnlineType {\n  username: string;\n  avatarUrl: string;\n  isOnline: boolean;\n  activeRoom?: {\n    name: string;\n    link?: string;\n  };\n}\n\nexport interface FriendsOnlineProps {\n  onlineFriendList: UserWithFollowInfo[];\n  onlineFriendCount?: number;\n  showMoreAction?: MouseEventHandler<HTMLDivElement>;\n}\n\nexport const FollowerOnline: React.FC<UserWithFollowInfo> = ({\n  username,\n  avatarUrl: avatar,\n  online,\n  currentRoom,\n}) => {\n  const navigation = useNavigation();\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const [clearChat] = useRoomChatStore((s) => [s.clearChat]);\n  const prefetch = useTypeSafePrefetch();\n\n  return (\n    <TouchableOpacity\n      style={{ flexDirection: \"row\", alignItems: \"center\", marginBottom: 20 }}\n    >\n      <SingleUserAvatar\n        size=\"sm\"\n        isOnline={online}\n        src={{ uri: avatar }}\n        // username={username}\n      />\n      <View style={{ paddingHorizontal: 10, flex: 1 }}>\n        <Text style={{ ...paragraphBold }} numberOfLines={1}>\n          {username}\n        </Text>\n        {currentRoom ? (\n          <Text\n            style={{ ...small, lineHeight: 14, color: colors.primary300 }}\n            numberOfLines={1}\n          >\n            {currentRoom.name}\n          </Text>\n        ) : (\n          <Text style={{ ...small, lineHeight: 14, color: colors.primary300 }}>\n            Not in a room\n          </Text>\n        )}\n      </View>\n      {currentRoom && (\n        <Button\n          title={\"Join room\"}\n          onPress={() => {\n            if (currentRoom.id !== currentRoomId) {\n              clearChat();\n              prefetch(\n                [\"joinRoomAndGetInfo\", currentRoom.id],\n                [currentRoom.id]\n              );\n            }\n            navigation.navigate(\"Room\", { roomId: currentRoom.id });\n          }}\n          size={\"small\"}\n          style={{ alignSelf: \"center\", marginLeft: 10 }}\n        />\n      )}\n    </TouchableOpacity>\n  );\n};\n\nexport const FollowersOnlineWrapper: React.FC<{\n  onlineFriendCount?: number;\n}> = ({ children }) => {\n  return (\n    <ScrollView style={styles.container}>\n      <Text style={styles.title}>People</Text>\n      {children}\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 20,\n    paddingTop: 10,\n  },\n  title: {\n    ...h3,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/following/FollowingOnlineController.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { StyleSheet, Text } from \"react-native\";\nimport { ScrollViewLoadMore } from \"../../components/ScrollViewLoadMore\";\nimport { colors, h3 } from \"../../constants/dogeStyle\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { FollowerOnline } from \"./FollowersOnline\";\n\nexport type FriendsOnlineControllerProps = {\n  cursor: number;\n  onLoad: (nextpage: number) => void;\n};\n\nconst Page: React.FC<FriendsOnlineControllerProps> = ({ cursor, onLoad }) => {\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"getMyFollowing\", cursor],\n    {\n      refetchOnWindowFocus: true,\n    },\n    [cursor]\n  );\n\n  const [loaded, setLoaded] = useState(false);\n  useEffect(() => {\n    if (data && !loaded) {\n      setLoaded(true);\n      onLoad(data.nextCursor);\n    }\n  }, [data, loaded, onLoad, setLoaded]);\n\n  if (cursor === 0 && !isLoading && !data?.users.length) {\n    return <Text>You have 0 friends online right now</Text>;\n  }\n\n  return (\n    <>\n      {data?.users.map((u) => (\n        <FollowerOnline {...u} key={u.id} />\n      ))}\n    </>\n  );\n};\n\nexport const FollowingOnlineController: React.FC<FriendsOnlineControllerProps> = ({}) => {\n  const [cursors, setCursors] = useState([0]);\n  const [isLoading, setLoading] = useState(false);\n  const [nextCursor, setNextCursor] = useState(null);\n\n  return (\n    <ScrollViewLoadMore\n      scrollViewProps={{ style: styles.container }}\n      shouldLoadMore={nextCursor != null}\n      isLoading={isLoading}\n      onLoadMore={() => {\n        setLoading(true);\n        setCursors([...cursors, nextCursor]);\n        setNextCursor(null);\n      }}\n    >\n      <Text style={styles.title}>People</Text>\n      {cursors.map((c) => (\n        <Page\n          key={c}\n          cursor={c}\n          onLoad={(nextpage) => {\n            setLoading(false);\n            if (nextpage) {\n              setNextCursor(nextpage);\n            }\n          }}\n        />\n      ))}\n    </ScrollViewLoadMore>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 20,\n    paddingTop: 10,\n  },\n  title: {\n    ...h3,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/help/HelpController.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nexport const HelpController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Help\"} showBackButton={true} />\n      <View style={styles.container} />\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/landing/LandingController.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  Platform,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { SignInButton } from \"../../components/buttons/SignInButton\";\nimport { colors, h3, small } from \"../../constants/dogeStyle\";\nimport { useSaveTokensFromQueryParams } from \"../auth/useSaveTokensFromQueryParams\";\n\nexport const LandingController: React.FC = () => {\n  useSaveTokensFromQueryParams();\n  const inset = useSafeAreaInsets();\n  return (\n    <>\n      <View\n        style={[\n          styles.container,\n          { paddingTop: inset.top, paddingBottom: inset.bottom },\n        ]}\n      >\n        <View\n          style={{\n            flex: 1,\n            alignItems: \"flex-start\",\n            justifyContent: \"center\",\n          }}\n        >\n          <Image\n            source={require(\"../../assets/images/Logo.png\")}\n            style={{ width: \"80%\", aspectRatio: 168 / 40, height: undefined }}\n          />\n        </View>\n        <View\n          style={{\n            flexGrow: 1,\n            alignItems: \"center\",\n            justifyContent: \"center\",\n          }}\n        >\n          <Text style={styles.title}>Welcome</Text>\n          <View\n            style={{\n              flexDirection: \"row\",\n              flexWrap: \"wrap\",\n              marginTop: 6,\n              marginBottom: 30,\n            }}\n          >\n            <Text style={styles.text}>By signing in you accept our </Text>\n            <TouchableOpacity>\n              <Text style={styles.link}>Privacy Policy</Text>\n            </TouchableOpacity>\n            <Text style={styles.text}> and </Text>\n            <TouchableOpacity style={{ padding: 0 }}>\n              <Text style={styles.link}>Terms of Service</Text>\n            </TouchableOpacity>\n          </View>\n          <SignInButton style={styles.signinButton} provider={\"github\"} />\n          <SignInButton style={styles.signinButton} provider={\"twitter\"} />\n          <SignInButton style={styles.signinButton} provider={\"google\"} />\n          {Platform.OS === \"ios\" && (\n            <SignInButton style={styles.signinButton} provider={\"apple\"} />\n          )}\n        </View>\n        <View\n          style={{\n            flex: 1,\n            flexDirection: \"row\",\n            alignItems: \"flex-end\",\n            justifyContent: \"space-evenly\",\n            paddingBottom: 20,\n          }}\n        >\n          <TouchableOpacity>\n            <Image\n              source={require(\"../../assets/images/github.png\")}\n              style={{ tintColor: colors.text }}\n            />\n          </TouchableOpacity>\n          <TouchableOpacity>\n            <Image\n              source={require(\"../../assets/images/twitter.png\")}\n              style={{ tintColor: colors.text }}\n            />\n          </TouchableOpacity>\n          <TouchableOpacity>\n            <Image\n              source={require(\"../../assets/images/discord.png\")}\n              style={{ tintColor: colors.text }}\n            />\n          </TouchableOpacity>\n        </View>\n      </View>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  title: {\n    ...h3,\n  },\n  text: {\n    ...small,\n  },\n  link: {\n    ...small,\n    color: colors.accent,\n    textAlignVertical: \"center\",\n  },\n  container: {\n    flex: 1,\n    backgroundColor: colors.primary800,\n    paddingHorizontal: 40,\n  },\n  signinButton: {\n    height: 50,\n    alignSelf: \"stretch\",\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/languages/LanguagesController.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nexport const LanguagesController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Languages\"} showBackButton={true} />\n      <View style={styles.container} />\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/main/MainController.tsx",
    "content": "import React from \"react\";\nimport { Header } from \"../../components/header/Header\";\nimport { BottomNavigator } from \"../../navigation/mainNavigator/BottomNavigator\";\nimport { WebRtcApp } from \"../webrtc/WebRtcApp\";\n\nexport const MainController: React.FC = () => {\n  return (\n    <>\n      <Header />\n      <BottomNavigator />\n      <WebRtcApp />\n    </>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/messages/MessagesController.tsx",
    "content": "import React from \"react\";\nimport { ScrollView, StyleSheet } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { MessageElement } from \"../../components/MessageElement\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nconst messageMocks = [\n  {\n    ts: Date.now() / 1000,\n    text: \"This is a good day to die\",\n  },\n  {\n    ts: Date.now() / 1000,\n    text: \"Life is amazing\",\n  },\n  {\n    ts: Date.now() / 1000,\n    text: \"Hey, I just met you, that wonderful how far we can go together\",\n  },\n  {\n    ts: Date.now() / 1000,\n    text:\n      \"RNCAsyncStorage, RNCMaskedView, RNGestureHandler, RNInAppBrowser, RNReanimated, RNScreens\",\n  },\n  {\n    ts: Date.now() / 1000,\n    text:\n      \"Resolving packages...[2/4] 🚚  Fetching packages...[3/4] 🔗  Linking dependencies...\",\n  },\n  {\n    ts: Date.now() / 1000,\n    text:\n      \"Pod installation complete! There are 58 dependencies from the Podfile and 49 total pods installed.\",\n  },\n];\n\nconst userMocks = {\n  username: \"DrMadTurkey\",\n  avatar: require(\"../../assets/images/100.png\"),\n  isOnline: true,\n};\n\nexport const MessagesController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Messages\"} showBackButton={true} />\n      <ScrollView style={styles.scrollView}>\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[0]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[1]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[2]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[3]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[4]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[5]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[0]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[1]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[2]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[3]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[4]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[5]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[0]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[1]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[2]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[3]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[4]}\n          style={{ marginBottom: 20 }}\n        />\n        <MessageElement\n          user={userMocks}\n          msg={messageMocks[5]}\n          style={{ marginBottom: 20 }}\n        />\n      </ScrollView>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 25,\n    paddingTop: 10,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/notifications/NotificationsController.tsx",
    "content": "import React from \"react\";\nimport { ScrollView, StyleSheet, Text } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { FollowNotification } from \"../../components/notifications/FollowNotification\";\nimport { LiveNotification } from \"../../components/notifications/LiveNotification\";\nimport { NewRoomNotification } from \"../../components/notifications/NewRoomNotification\";\nimport { colors, h4 } from \"../../constants/dogeStyle\";\n\nexport const NotificationsController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Notifications\"} showBackButton={true} />\n      <ScrollView style={styles.scrollView}>\n        <Text style={{ ...h4, marginTop: 10, marginBottom: 20 }}>Today</Text>\n        <NewRoomNotification\n          username={\"DrMadWithAVeryLongLongLongTurkey\"}\n          time={\"now\"}\n          joined={true}\n          style={{ marginBottom: 27 }}\n        />\n        <LiveNotification\n          username={\"DrMadTurkey\"}\n          time={\"now\"}\n          joined={true}\n          style={{ marginBottom: 27 }}\n        />\n        <FollowNotification\n          username={\"DrMadTurkey\"}\n          userAvatarSrc={require(\"../../assets/images/100.png\")}\n          time={\"now\"}\n          isOnline={true}\n          following={true}\n          style={{ marginBottom: 27 }}\n        />\n        <FollowNotification\n          username={\"DrMadTurkey\"}\n          userAvatarSrc={require(\"../../assets/images/100.png\")}\n          time={\"now\"}\n          isOnline={true}\n          following={false}\n          style={{ marginBottom: 27 }}\n        />\n        <FollowNotification\n          username={\"DrMadTurkey\"}\n          userAvatarSrc={require(\"../../assets/images/100.png\")}\n          time={\"now\"}\n          isOnline={true}\n          following={true}\n          style={{ marginBottom: 27 }}\n        />\n        <FollowNotification\n          username={\"DrMadTurkey\"}\n          userAvatarSrc={require(\"../../assets/images/100.png\")}\n          time={\"now\"}\n          isOnline={true}\n          following={true}\n          style={{ marginBottom: 27 }}\n        />\n      </ScrollView>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 25,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/profile/ProfileController.tsx",
    "content": "import { useNavigation } from \"@react-navigation/core\";\nimport React from \"react\";\nimport { StyleSheet, Text, TouchableOpacity, View } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { colors, fontFamily } from \"../../constants/dogeStyle\";\nimport { useTokenStore } from \"../auth/useTokenStore\";\n\nexport const ProfileController: React.FC = () => {\n  const setTokens = useTokenStore((s) => s.setTokens);\n  const navigation = useNavigation();\n  return (\n    <>\n      <TitledHeader title={\"Profile\"} showBackButton={true} />\n      <View style={styles.container}>\n        <TouchableOpacity\n          onPress={() => {\n            setTokens({ accessToken: \"\", refreshToken: \"\" });\n            navigation.navigate(\"Home\");\n          }}\n        >\n          <Text\n            style={{\n              alignSelf: \"center\",\n              fontFamily: fontFamily.extraBold,\n              color: colors.text,\n            }}\n          >\n            Logout\n          </Text>\n        </TouchableOpacity>\n      </View>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/reportBug/ReportBugController.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nexport const ReportBugController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Report bug\"} showBackButton={true} />\n      <View style={styles.container} />\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/InviteRoomController.tsx",
    "content": "import { Room } from \"@dogehouse/kebab\";\nimport React, { useEffect, useState } from \"react\";\nimport { Share, StyleSheet, Text, View, ViewStyle } from \"react-native\";\nimport { SingleUserAvatar } from \"../../components/avatars/SingleUserAvatar\";\nimport { Button } from \"../../components/buttons/Button\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { ScrollViewLoadMore } from \"../../components/ScrollViewLoadMore\";\nimport { Spinner } from \"../../components/Spinner\";\nimport { colors, paragraph, small } from \"../../constants/dogeStyle\";\nimport { useWrappedConn } from \"../../shared-hooks/useConn\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\n\nconst InviteButton: React.FC<{ style: ViewStyle; onPress: () => void }> = ({\n  style,\n  onPress,\n}) => {\n  const [invited, setInvited] = useState(false);\n  return (\n    <Button\n      style={style}\n      size=\"small\"\n      disabled={invited}\n      onPress={() => {\n        onPress();\n        setInvited(true);\n      }}\n      title={invited ? \"Invited\" : \"Invite\"}\n    />\n  );\n};\n\nconst Page = ({\n  cursor,\n  onLoad,\n}: {\n  cursor: number;\n  onLoad: (nextpage: number) => void;\n}) => {\n  const conn = useWrappedConn();\n  const { isLoading, data } = useTypeSafeQuery(\n    [\"getInviteList\", cursor],\n    {\n      staleTime: Infinity,\n      enabled: true,\n      refetchOnMount: \"always\",\n    },\n    [cursor]\n  );\n  const [loaded, setLoaded] = useState(false);\n  useEffect(() => {\n    if (data && !loaded) {\n      setLoaded(true);\n      onLoad(data.nextCursor);\n    }\n  }, [data, loaded, onLoad, setLoaded]);\n  if (isLoading) {\n    return (\n      <View\n        style={{\n          flex: 1,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          backgroundColor: colors.primary900,\n        }}\n      >\n        <Spinner />\n      </View>\n    );\n  }\n\n  if (!data) {\n    return null;\n  }\n\n  return (\n    <>\n      {data.users.map((user) => (\n        <View\n          key={user.id}\n          style={{\n            flexDirection: \"row\",\n            alignItems: \"center\",\n            marginBottom: 20,\n          }}\n        >\n          <SingleUserAvatar size=\"sm\" src={{ uri: user.avatarUrl }} />\n          <View style={{ paddingHorizontal: 10, flex: 1 }}>\n            <Text style={{ ...paragraph }} numberOfLines={1}>\n              {user.displayName}\n            </Text>\n            <Text\n              style={{ ...small, lineHeight: 14, color: colors.primary300 }}\n              numberOfLines={1}\n            >\n              @{user.username}\n            </Text>\n          </View>\n          <InviteButton\n            onPress={() => conn.mutation.inviteToRoom(user.id)}\n            style={{ alignSelf: \"center\", marginLeft: 10 }}\n          />\n        </View>\n      ))}\n    </>\n  );\n};\n\ntype InviteRoomControllerPropse = {\n  room: Room;\n};\n\nconst onShare = async (message: string) => {\n  try {\n    await Share.share({\n      message,\n    });\n  } catch (error) {\n    console.error(\"Sharing failed with : \", error);\n  }\n};\n\nexport const InviteRoomController: React.FC<InviteRoomControllerPropse> = ({\n  room,\n}) => {\n  const [cursors, setCursors] = useState([0]);\n  const [isLoading, setLoading] = useState(false);\n  const [nextCursor, setNextCursor] = useState(null);\n  const url = `https://next.dogehouse.tv/room/${room.id}`;\n\n  return (\n    <>\n      <TitledHeader title={\"Invite people\"} showBackButton={true} />\n      <View style={styles.container}>\n        {room.isPrivate ? null : (\n          <>\n            <Button\n              size=\"big\"\n              onPress={() => {\n                onShare(url);\n              }}\n              title={\"Send a link\"}\n              style={{ alignSelf: \"center\", marginBottom: 20 }}\n            />\n          </>\n        )}\n        <ScrollViewLoadMore\n          shouldLoadMore={nextCursor != null}\n          isLoading={isLoading}\n          onLoadMore={() => {\n            setLoading(true);\n            setCursors([...cursors, nextCursor]);\n            setNextCursor(null);\n          }}\n        >\n          {cursors.map((cursor) => (\n            <Page\n              key={cursor}\n              cursor={cursor}\n              onLoad={(c) => {\n                setLoading(false);\n                if (c) {\n                  setNextCursor(c);\n                }\n              }}\n            />\n          ))}\n        </ScrollViewLoadMore>\n      </View>\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/MinimizedRoomCardController.tsx",
    "content": "import React from \"react\";\nimport { MinimizedRoomCard } from \"../../components/minimizedRoomCard/MinimizedRoomCard\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport * as RootNavigation from \"../../navigation/RootNavigation\";\nimport { useSetMute } from \"../../shared-hooks/useSetMute\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { useOnRoomPage } from \"./useOnRoomPage\";\n\ninterface MinimizedRoomCardControllerProps {}\n\nconst MinimizedRoomCardController: React.FC<MinimizedRoomCardControllerProps> = () => {\n  const setInternalMute = useSetMute();\n  const muted = useMuteStore((s) => s.muted);\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const { data } = useTypeSafeQuery([\"joinRoomAndGetInfo\", currentRoomId], {}, [\n    currentRoomId,\n  ]);\n\n  const { onRoomPage } = useOnRoomPage();\n\n  if (onRoomPage || !currentRoomId || !data || \"error\" in data) {\n    return null;\n  }\n\n  const { room } = data;\n  const dt = new Date(room.inserted_at);\n\n  return (\n    <MinimizedRoomCard\n      onPress={() => RootNavigation.navigate(\"Room\", { roomId: currentRoomId })}\n      room={{\n        name: room.name,\n        // @TODO: Get avatars from `peoplePreviewList` when they are available\n        speakerAvatars: [],\n        roomStartedAt: dt,\n        myself: {\n          isDeafened: false,\n          isMuted: muted,\n          switchMuted: () => {\n            setInternalMute(!muted);\n          },\n        },\n      }}\n      style={{\n        position: \"absolute\",\n        bottom: 90,\n        right: 20,\n        zIndex: 10,\n      }}\n    />\n  );\n};\n\nexport default MinimizedRoomCardController;\n"
  },
  {
    "path": "pilaf/src/modules/room/RoomController.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport { useFocusEffect, useNavigation } from \"@react-navigation/native\";\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { View } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport BottomSheet from \"reanimated-bottom-sheet\";\nimport { validate as uuidValidate } from \"uuid\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { Spinner } from \"../../components/Spinner\";\nimport { colors } from \"../../constants/dogeStyle\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { RoomNavigator } from \"../../navigation/mainNavigator/RoomNavigator\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { WaitForWsAndAuth } from \"../auth/WaitForWsAndAuth\";\nimport { RoomChat } from \"./chat/RoomChat\";\nimport { useOnRoomPage } from \"./useOnRoomPage\";\n\nconst placeHolder = (\n  <View\n    style={{\n      flex: 1,\n      backgroundColor: colors.primary900,\n    }}\n  >\n    <TitledHeader title={\"\"} showBackButton={true} />\n    <View style={{ flex: 1, justifyContent: \"center\", alignItems: \"center\" }}>\n      <Spinner size={\"m\"} />\n    </View>\n  </View>\n);\n\nexport type RoomControllerProps = {\n  roomId: string;\n};\n\nexport const RoomController: React.FC<RoomControllerProps> = ({ roomId }) => {\n  // const conn = useWrappedConn();\n  const { setOnRoomPage } = useOnRoomPage();\n  const { currentRoomId, setCurrentRoomId } = useCurrentRoomIdStore();\n  const navigation = useNavigation();\n  const sheetRef = useRef(null);\n  const inset = useSafeAreaInsets();\n  useFocusEffect(\n    useCallback(() => {\n      setOnRoomPage(true);\n      return () => {\n        setOnRoomPage(false);\n      };\n    }, [setOnRoomPage])\n  );\n  const { data, isLoading } = useTypeSafeQuery(\n    [\"joinRoomAndGetInfo\", roomId || \"\"],\n    {\n      enabled: uuidValidate(roomId),\n      onSuccess: ((d: JoinRoomAndGetInfoResponse | { error: string }) => {\n        if (!(\"error\" in d)) {\n          setCurrentRoomId(() => d.room.id);\n        }\n      }) as any,\n    },\n    [roomId]\n  );\n\n  useEffect(() => {\n    if (isLoading) {\n      return;\n    }\n    if (!data) {\n      setCurrentRoomId(null);\n      navigation.navigate(\"Home\");\n      return;\n    }\n    if (\"error\" in data) {\n      setCurrentRoomId(null);\n      //showErrorToast(data.error);\n      navigation.navigate(\"Home\");\n    }\n  }, [data, isLoading, navigation, navigation.navigate, setCurrentRoomId]);\n\n  if (isLoading || !currentRoomId) {\n    return placeHolder;\n  }\n\n  if (!data || \"error\" in data) {\n    return null;\n  }\n\n  const renderChat = () => (\n    <RoomChat {...data} wrapperRef={sheetRef} style={{ height: \"100%\" }} />\n  );\n  return (\n    <WaitForWsAndAuth>\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: colors.primary900,\n          paddingBottom: 79 + inset.bottom,\n        }}\n      >\n        <RoomNavigator data={data} />\n      </View>\n      <BottomSheet\n        ref={sheetRef}\n        snapPoints={[\"95%\", 79 + inset.bottom + 20]}\n        initialSnap={1}\n        borderRadius={20}\n        renderContent={renderChat}\n      />\n    </WaitForWsAndAuth>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/RoomDescriptionController.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport React, { useState } from \"react\";\nimport { ScrollView, Text, View } from \"react-native\";\nimport { SearchHeader } from \"../../components/header/SearchHeader\";\nimport { UserSearchResult } from \"../../components/search/UserSearchResult\";\nimport {\n  colors,\n  h4,\n  paragraph,\n  paragraphBold,\n  small,\n  smallBold,\n} from \"../../constants/dogeStyle\";\n\ntype RoomDescriptionControllerProps = {\n  data: JoinRoomAndGetInfoResponse;\n};\n\nexport const RoomDescriptionController: React.FC<RoomDescriptionControllerProps> = ({\n  data,\n}) => {\n  const [query, setQuery] = useState(\"\");\n\n  return (\n    <>\n      <SearchHeader\n        onTextChange={setQuery}\n        text={query}\n        autoFocus={false}\n        placeHolder={\"Search in the room\"}\n      />\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: colors.primary900,\n          paddingVertical: 10,\n        }}\n      >\n        <View\n          style={{\n            paddingHorizontal: 25,\n          }}\n        >\n          <Text style={{ ...h4 }}>{data.room.name}</Text>\n          <Text style={{ ...small }}>\n            with{\" \"}\n            <Text style={{ ...smallBold }}>\n              {data.users.find((u) => u.id === data.room.creatorId).displayName}\n            </Text>\n          </Text>\n          {data.room.description ? (\n            <Text\n              style={{ ...paragraph, color: colors.primary300, marginTop: 10 }}\n            >\n              {data.room.description}\n            </Text>\n          ) : (\n            <></>\n          )}\n        </View>\n        <View\n          style={{\n            backgroundColor: colors.primary300,\n            height: 0.5,\n            marginVertical: 20,\n          }}\n        />\n        <ScrollView>\n          {query.length === 0 && (\n            <View style={{ paddingHorizontal: 20 }}>\n              <Text style={{ ...paragraphBold, marginBottom: 10 }}>\n                Speakers\n              </Text>\n              {data.users\n                .filter(\n                  (u) =>\n                    u.id === data.room.creatorId || u.roomPermissions?.isSpeaker\n                )\n                .map((u) => (\n                  <UserSearchResult\n                    key={u.id}\n                    userAvatarSrc={{ uri: u.avatarUrl }}\n                    userName={u.username}\n                    isOnline={true}\n                    userLink={u.username}\n                  />\n                ))}\n              <Text style={{ ...paragraphBold, marginVertical: 10 }}>\n                Listeners\n              </Text>\n              {data.users\n                .filter(\n                  (u) =>\n                    u.id !== data.room.creatorId &&\n                    !u.roomPermissions?.isSpeaker\n                )\n                .map((u) => (\n                  <UserSearchResult\n                    key={u.id}\n                    userAvatarSrc={{ uri: u.avatarUrl }}\n                    userName={u.username}\n                    isOnline={true}\n                    userLink={u.username}\n                  />\n                ))}\n            </View>\n          )}\n        </ScrollView>\n      </View>\n    </>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/RoomPanelController.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport { RouteProp } from \"@react-navigation/native\";\nimport React from \"react\";\nimport { ScrollView, StyleSheet, View } from \"react-native\";\nimport { validate as uuidValidate } from \"uuid\";\nimport { RoomHeader } from \"../../components/header/RoomHeader\";\nimport { colors } from \"../../constants/dogeStyle\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { RoomStackParamList } from \"../../navigation/mainNavigator/RoomNavigator\";\nimport { useTypeSafeMutation } from \"../../shared-hooks/useTypeSafeMutation\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\nimport { RoomUsersPanel } from \"./RoomUsersPanel\";\n\ntype RoomPageRouteProp = RouteProp<RoomStackParamList, \"RoomMain\">;\n\ntype RoomPageProps = {\n  route: RoomPageRouteProp;\n};\n\nexport const RoomPanelController: React.FC<RoomPageProps> = ({ route }) => {\n  const { mutateAsync: leaveRoom } = useTypeSafeMutation(\"leaveRoom\");\n  const navigation = useNavigation();\n  const { setCurrentRoomId } = useCurrentRoomIdStore();\n  const { data } = useTypeSafeQuery(\n    [\"joinRoomAndGetInfo\", route.params.data.room.id || \"\"],\n    {\n      enabled: uuidValidate(route.params.data.room.id),\n      onSuccess: ((d: JoinRoomAndGetInfoResponse | { error: string }) => {\n        if (!(\"error\" in d)) {\n          setCurrentRoomId(() => d.room.id);\n        }\n      }) as any,\n    },\n    [route.params.data.room.id]\n  );\n\n  if (!data || \"error\" in data) {\n    return null;\n  }\n\n  return (\n    <>\n      <View style={{ flex: 1, backgroundColor: colors.primary900 }}>\n        <RoomHeader\n          onLeavePress={() => {\n            leaveRoom([]);\n            setCurrentRoomId(null);\n            navigation.navigate(\"Home\");\n          }}\n          onTitlePress={() => {\n            navigation.navigate(\"RoomDescription\", { data: data });\n          }}\n          roomTitle={data.room.name}\n          roomSubtitle={data.room.peoplePreviewList\n            .filter((u) => u.id === data.room.creatorId)\n            .map((u) => u.displayName)\n            .join(\", \")}\n        />\n        <ScrollView\n          style={styles.scrollView}\n          contentContainerStyle={[styles.avatarsContainer]}\n        >\n          <RoomUsersPanel {...data} />\n        </ScrollView>\n      </View>\n      {/* <UserPreviewModal {...route.params} /> */}\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    padding: 20,\n    flex: 1,\n  },\n  avatarsContainer: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n  },\n  avatar: {\n    marginRight: 10,\n    marginBottom: 10,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/RoomUsersPanel.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { StyleSheet, Text, View } from \"react-native\";\nimport { Tag } from \"../../components/Tag\";\nimport { h4, smallBold } from \"../../constants/dogeStyle\";\nimport { useSplitUsersIntoSections } from \"./useSplitUsersIntoSections\";\n\ninterface RoomUsersPanelProps extends JoinRoomAndGetInfoResponse {}\n\nexport const RoomUsersPanel: React.FC<RoomUsersPanelProps> = (props) => {\n  const { askingToSpeak, listeners, speakers } = useSplitUsersIntoSections(\n    props\n  );\n  return (\n    <View style={{ paddingBottom: 20 }}>\n      <View\n        style={{\n          flexDirection: \"row\",\n          marginBottom: 20,\n        }}\n      >\n        <Text style={{ ...h4 }}>Speakers</Text>\n        <Tag style={{ marginLeft: 10, alignSelf: \"center\", height: 18 }}>\n          <Text style={{ ...smallBold, lineHeight: 18 }}>\n            {speakers.length}\n          </Text>\n        </Tag>\n      </View>\n      <View style={styles.avatarsContainer}>{speakers}</View>\n      {askingToSpeak.length ? (\n        <View\n          style={{\n            flexDirection: \"row\",\n            marginBottom: 20,\n          }}\n        >\n          <Text style={{ ...h4 }}>Asking to speak</Text>\n          <Tag style={{ marginLeft: 10, alignSelf: \"center\", height: 18 }}>\n            <Text style={{ ...smallBold, lineHeight: 18 }}>\n              {askingToSpeak.length}\n            </Text>\n          </Tag>\n        </View>\n      ) : null}\n      <View style={styles.avatarsContainer}>{askingToSpeak}</View>\n      {listeners.length ? (\n        <View\n          style={{\n            flexDirection: \"row\",\n            marginBottom: 20,\n          }}\n        >\n          <Text style={{ ...h4 }}>Listeners</Text>\n          <Tag style={{ marginLeft: 10, alignSelf: \"center\", height: 18 }}>\n            <Text style={{ ...smallBold, lineHeight: 18 }}>\n              {listeners.length}\n            </Text>\n          </Tag>\n        </View>\n      ) : null}\n      <View style={styles.avatarsContainer}>{listeners}</View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    padding: 20,\n    flex: 1,\n  },\n  avatarsContainer: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"flex-start\",\n  },\n  avatar: {\n    marginRight: 10,\n    marginBottom: 10,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/UserPreviewModalProvider.tsx",
    "content": "import React, { useMemo, useState } from \"react\";\nimport { RoomChatMessage } from \"./chat/useRoomChatStore\";\n\ninterface UserProfileOverlayProviderProps {}\n\ntype Data = { userId: string; message?: RoomChatMessage };\n\nexport const UserPreviewModalContext = React.createContext<{\n  data?: Data | null;\n  setData: (x: Data | null) => void;\n}>({ setData: () => {} });\n\nexport const UserPreviewModalProvider: React.FC<UserProfileOverlayProviderProps> = ({\n  children,\n}) => {\n  const [data, setData] = useState<Data | null>(null);\n  return (\n    <UserPreviewModalContext.Provider\n      value={useMemo(() => ({ data, setData }), [data, setData])}\n    >\n      {children}\n    </UserPreviewModalContext.Provider>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/EmoteData.ts",
    "content": "import { ImageSourcePropType } from \"react-native\";\n\nexport const customEmojis = [\n  {\n    name: \"heart\",\n    short_names: [\"heart\", \"<3\"],\n    keywords: [\"heart\", \"<3\"],\n    imageUrl: require(\"../../../assets/images/emotes/heart.png\"),\n  },\n  {\n    name: \"brokenHeart\",\n    short_names: [\"brokenHeart\"],\n    keywords: [\"broken\", \"heart\", \"broken heart\", \"brokenHeart\"],\n    imageUrl: require(\"../../../assets/images/emotes/brokenHeart.gif\"),\n  },\n  {\n    name: \"obamium\",\n    short_names: [\"obama\"],\n    keywords: [\"obama\", \"prism\", \"obamium\"],\n    imageUrl: require(\"../../../assets/images/emotes/obamium.png\"),\n  },\n  {\n    name: \"IntelxAMD\",\n    short_names: [\"intel\"],\n    keywords: [\"intel\", \"amd\", \"intelxamd\"],\n    imageUrl: require(\"../../../assets/images/emotes/IntelxAMD.png\"),\n  },\n  {\n    name: \"linus\",\n    short_names: [\"linus\"],\n    keywords: [\"linus\", \"tech\", \"linustechtips\"],\n    imageUrl: require(\"../../../assets/images/emotes/linus.png\"),\n  },\n  {\n    name: \"redDogeHouse\",\n    short_names: [\"redDogeHouse\"],\n    keywords: [\"red\", \"dogehouse\", \"doge\"],\n    imageUrl: require(\"../../../assets/images/emotes/reddogehouse.png\"),\n  },\n  {\n    name: \"this\",\n    short_names: [\"this\"],\n    keywords: [\"this\"],\n    imageUrl: require(\"../../../assets/images/emotes/this.png\"),\n  },\n  {\n    name: \"brownDogeHouse\",\n    short_names: [\"brownDogeHouse\"],\n    keywords: [\"brown\", \"dogehouse\", \"doge\"],\n    imageUrl: require(\"../../../assets/images/emotes/browndogehouse.png\"),\n  },\n  {\n    name: \"shut\",\n    short_names: [\"shut\"],\n    keywords: [\"shut\"],\n    imageUrl: require(\"../../../assets/images/emotes/shut.png\"),\n  },\n  {\n    name: \"blobWtf\",\n    short_names: [\"blobWtf\"],\n    keywords: [\"blob\", \"wtf\"],\n    imageUrl: require(\"../../../assets/images/emotes/blobwtf.png\"),\n  },\n  {\n    name: \"WhaleThonk\",\n    short_names: [\"WhaleThonk\"],\n    keywords: [\"whale\", \"thonk\", \"thinking\", \"what\"],\n    imageUrl: require(\"../../../assets/images/emotes/whalethonk.png\"),\n  },\n  {\n    name: \"PogChamp\",\n    short_names: [\"PogChamp\"],\n    keywords: [\"pogchamp\", \"pog\"],\n    imageUrl: require(\"../../../assets/images/emotes/pogchamp.png\"),\n  },\n  {\n    name: \"monkaS\",\n    short_names: [\"monkaS\"],\n    keywords: [\"monkas\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/monkas.png\"),\n  },\n  {\n    name: \"HYPERS\",\n    short_names: [\"HYPERS\"],\n    keywords: [\"hypers\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/hypers.png\"),\n  },\n  {\n    name: \"peped\",\n    short_names: [\"peped\"],\n    keywords: [\"peped\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/peped.gif\"),\n  },\n  {\n    name: \"Pepega\",\n    short_names: [\"Pepega\"],\n    keywords: [\"pepega\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepega.png\"),\n  },\n  {\n    name: \"peepoHappy\",\n    short_names: [\"peepoHappy\"],\n    keywords: [\"peepohappy\", \"peepo\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepohappy.png\"),\n  },\n  {\n    name: \"peepoHug\",\n    short_names: [\"peepoHug\"],\n    keywords: [\"peepohug\", \"peepo\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepohug.png\"),\n  },\n  {\n    name: \"Sadge\",\n    short_names: [\"Sadge\"],\n    keywords: [\"sadge\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/sadge.png\"),\n  },\n  {\n    name: \"catJAM\",\n    short_names: [\"catJAM\"],\n    keywords: [\"catjam\", \"vibe\"],\n    imageUrl: require(\"../../../assets/images/emotes/catjam.gif\"),\n  },\n  {\n    name: \"Thonk\",\n    short_names: [\"Thonk\"],\n    keywords: [\"thonk\", \"think\"],\n    imageUrl: require(\"../../../assets/images/emotes/thonk.png\"),\n  },\n  {\n    name: \"DogeHouse\",\n    short_names: [\"DogeHouse\"],\n    keywords: [\"dogehouse\", \"doge\"],\n    imageUrl: require(\"../../../assets/images/emotes/dogehouse.png\"),\n  },\n  {\n    name: \"SadHouse\",\n    short_names: [\"SadHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"sadhouse\"],\n    imageUrl: require(\"../../../assets/images/emotes/sadhouse.png\"),\n  },\n  {\n    name: \"CoolHouse\",\n    short_names: [\"CoolHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"coolhouse\"],\n    imageUrl: require(\"../../../assets/images/emotes/coolhouse.png\"),\n  },\n  {\n    name: \"WinkHouse\",\n    short_names: [\"WinkHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"winkhouse\"],\n    imageUrl: require(\"../../../assets/images/emotes/winkhouse.png\"),\n  },\n  {\n    name: \"SupriseHouse\",\n    short_names: [\"SupriseHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"suprisehouse\", \"shock\"],\n    imageUrl: require(\"../../../assets/images/emotes/suprisehouse.png\"),\n  },\n  {\n    name: \"NeutralHouse\",\n    short_names: [\"NeutralHouse\"],\n    keywords: [\"dogehouse\", \"doge\", \"neutralhouse\"],\n    imageUrl: require(\"../../../assets/images/emotes/neutralhouse.png\"),\n  },\n  {\n    name: \"WAYTOODANK\",\n    short_names: [\"WAYTOODANK\"],\n    keywords: [\"dank\", \"feelsdankman\", \"waytoodank\"],\n    imageUrl: require(\"../../../assets/images/emotes/waytoodank.gif\"),\n  },\n  {\n    name: \"CryptoBTC\",\n    short_names: [\"CryptoBTC\"],\n    keywords: [\"crypto\", \"btc\", \"bitcoin\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoBTC.png\"),\n  },\n  {\n    name: \"CryptoETH\",\n    short_names: [\"CryptoETH\"],\n    keywords: [\"crypto\", \"eth\", \"ethereum\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoETH.png\"),\n  },\n  {\n    name: \"CryptoBNB\",\n    short_names: [\"CryptoBNB\"],\n    keywords: [\"crypto\", \"bnb\", \"binance\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoBNB.png\"),\n  },\n  {\n    name: \"CryptoLTC\",\n    short_names: [\"CryptoLTC\"],\n    keywords: [\"crypto\", \"ltc\", \"litecoin\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoLTC.png\"),\n  },\n  {\n    name: \"CryptoBCH\",\n    short_names: [\"CryptoBCH\"],\n    keywords: [\"crypto\", \"bch\", \"bitcoin\", \"bitcoincash\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoBCH.png\"),\n  },\n  {\n    name: \"CryptoDOGE\",\n    short_names: [\"CryptoDOGE\"],\n    keywords: [\"crypto\", \"doge\", \"dogecoin\", \"bestcoin\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoDOGE.png\"),\n  },\n  {\n    name: \"CryptoSUSHI\",\n    short_names: [\"CryptoSUSHI\"],\n    keywords: [\"crypto\", \"sushi\", \"swap\", \"sushiswap\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoSUSHI.png\"),\n  },\n  {\n    name: \"CryptoTRON\",\n    short_names: [\"CryptoTRON\"],\n    keywords: [\"crypto\", \"tron\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoTron.png\"),\n  },\n  {\n    name: \"CryptoZEC\",\n    short_names: [\"CryptoZEC\"],\n    keywords: [\"crypto\", \"zec\", \"zcash\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoZEC.png\"),\n  },\n  {\n    name: \"CryptoETC\",\n    short_names: [\"CryptoETC\"],\n    keywords: [\"crypto\", \"etc\", \"ethereum\", \"ethereumclassic\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoETC.png\"),\n  },\n  {\n    name: \"CryptoCAKE\",\n    short_names: [\"CryptoCAKE\"],\n    keywords: [\"crypto\", \"cake\", \"swap\", \"pancakeswap\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoCAKE.png\"),\n  },\n  {\n    name: \"CryptoADA\",\n    short_names: [\"CryptoADA\"],\n    keywords: [\"crypto\", \"ada\", \"cardano\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoADA.png\"),\n  },\n  {\n    name: \"CryptoXRP\",\n    short_names: [\"CryptoXRP\"],\n    keywords: [\"crypto\", \"xrp\", \"ripple\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoXRP.png\"),\n  },\n  {\n    name: \"CryptoUSDC\",\n    short_names: [\"CryptoUSDC\"],\n    keywords: [\"crypto\", \"usdc\", \"usdcoin\"],\n    imageUrl: require(\"../../../assets/images/emotes/cryptoUSDC.png\"),\n  },\n  {\n    name: \"DodgyCoin\",\n    short_names: [\"DodgyCoin\"],\n    keywords: [\"crypto\", \"doge\", \"dodgycoin\"],\n    imageUrl: require(\"../../../assets/images/emotes/dodgyCoin.png\"),\n  },\n  {\n    name: \"pepeBCKL\",\n    short_names: [\"pepeBCKL\"],\n    keywords: [\n      \"pepebckl\",\n      \"bckl\",\n      \"pepe\",\n      \"malarkey\",\n      \"jesse\",\n      \"penguin\",\n      \"stallman\",\n      \"freesoftware\",\n      \"fsf\",\n      \"charlie\",\n      \"kernel\",\n      \"lab\",\n      \"kernellab\",\n      \"computing\",\n      \"jessecharlie\",\n      \"linux\",\n      \"torvalds\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/pepeBCKL.png\"),\n  },\n  {\n    name: \"5Head\",\n    short_names: [\"5Head\"],\n    keywords: [\"5head\", \"big\", \"brain\", \"bigbrain\", \"smart\"],\n    imageUrl: require(\"../../../assets/images/emotes/5Head.png\"),\n  },\n  {\n    name: \"AYAYA\",\n    short_names: [\"AYAYA\"],\n    keywords: [\"ayaya\"],\n    imageUrl: require(\"../../../assets/images/emotes/AYAYA.png\"),\n  },\n  {\n    name: \"babaYEP\",\n    short_names: [\"babaYEP\"],\n    keywords: [\"babayep\", \"baba\", \"yep\"],\n    imageUrl: require(\"../../../assets/images/emotes/babaYEP.png\"),\n  },\n  {\n    name: \"BBoomer\",\n    short_names: [\"BBoomer\"],\n    keywords: [\"bboomer\", \"boomer\"],\n    imageUrl: require(\"../../../assets/images/emotes/BBoomer.gif\"),\n  },\n  {\n    name: \"BebeLa\",\n    short_names: [\"BebeLa\"],\n    keywords: [\"bebela\", \"bebe\"],\n    imageUrl: require(\"../../../assets/images/emotes/BebeLa.png\"),\n  },\n  {\n    name: \"BERN\",\n    short_names: [\"BERN\"],\n    keywords: [\n      \"bern\",\n      \"notmeus\",\n      \"bernie\",\n      \"sanders\",\n      \"berniesanders\",\n      \"bernard\",\n      \"socialism\",\n      \"socialist\",\n      \"comrade\",\n      \"democrat\",\n      \"malarkey\",\n      \"biden\",\n      \"joe\",\n      \"joebiden\",\n      \"us\",\n      \"usa\",\n      \"america\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/BERN.png\"),\n  },\n  {\n    name: \"bidenJAM\",\n    short_names: [\"bidenJAM\"],\n    keywords: [\n      \"bidenjam\",\n      \"jam\",\n      \"jamjam\",\n      \"malarkey\",\n      \"ridinwithbiden\",\n      \"icecream\",\n      \"joe\",\n      \"biden\",\n      \"joebiden\",\n      \"46\",\n      \"president\",\n      \"laser\",\n      \"potus\",\n      \"democrat\",\n      \"us\",\n      \"usa\",\n      \"america\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/bidenJAM.gif\"),\n  },\n  {\n    name: \"Playa\",\n    short_names: [\"Playa\"],\n    keywords: [\"Playa\"],\n    imageUrl: require(\"../../../assets/images/emotes/Playa.png\"),\n  },\n  {\n    name: \"IGetIt\",\n    short_names: [\"IGetIt\"],\n    keywords: [\"IGetIt\", \"Smile\", \"PepeLa\", \"Smug\"],\n    imageUrl: require(\"../../../assets/images/emotes/IGetIt.png\"),\n  },\n  {\n    name: \"BLANKIES\",\n    short_names: [\"BLANKIES\"],\n    keywords: [\"blankies\", \"jammies\", \"blanket\", \"dance\"],\n    imageUrl: require(\"../../../assets/images/emotes/BLANKIES.gif\"),\n  },\n  {\n    name: \"BOGGED\",\n    short_names: [\"BOGGED\"],\n    keywords: [\n      \"bogged\",\n      \"bog\",\n      \"hello\",\n      \"phone\",\n      \"iphone\",\n      \"calling\",\n      \"call\",\n      \"phonecall\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/BOGGED.png\"),\n  },\n  {\n    name: \"Clap\",\n    short_names: [\"Clap\"],\n    keywords: [\"clap\", \"clapping\"],\n    imageUrl: require(\"../../../assets/images/emotes/Clap.gif\"),\n  },\n  {\n    name: \"COGGERS\",\n    short_names: [\"COGGERS\"],\n    keywords: [\"coggers\", \"pog\", \"pogu\", \"poggies\", \"poggers\", \"pogger\"],\n    imageUrl: require(\"../../../assets/images/emotes/COGGERS.gif\"),\n  },\n  {\n    name: \"COPIUM\",\n    short_names: [\"COPIUM\"],\n    keywords: [\"copium\", \"cope\"],\n    imageUrl: require(\"../../../assets/images/emotes/COPIUM.png\"),\n  },\n  {\n    name: \"coronaS\",\n    short_names: [\"coronaS\"],\n    keywords: [\"coronas\", \"corona\", \"mask\", \"covid\"],\n    imageUrl: require(\"../../../assets/images/emotes/coronaS.png\"),\n  },\n  {\n    name: \"CrabPls\",\n    short_names: [\"CrabPls\"],\n    keywords: [\"crabpls\", \"crab\", \"pls\", \"plz\", \"please\"],\n    imageUrl: require(\"../../../assets/images/emotes/crabpls.gif\"),\n  },\n  {\n    name: \"DANKHACKERMANS\",\n    short_names: [\"DANKHACKERMANS\"],\n    keywords: [\n      \"dankhackermans\",\n      \"dank\",\n      \"hacker\",\n      \"hackermans\",\n      \"keyboard\",\n      \"typing\",\n      \"computer\",\n      \"coding\",\n      \"coder\",\n      \"code\",\n      \"virus\",\n      \"malware\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/DANKHACKERMANS.gif\"),\n  },\n  {\n    name: \"dankHug\",\n    short_names: [\"dankHug\"],\n    keywords: [\"dankhug\", \"dank\", \"hug\"],\n    imageUrl: require(\"../../../assets/images/emotes/dankHug.png\"),\n  },\n  {\n    name: \"DANKIES\",\n    short_names: [\"DANKIES\"],\n    keywords: [\"dankies\", \"dank\"],\n    imageUrl: require(\"../../../assets/images/emotes/DANKIES.gif\"),\n  },\n  {\n    name: \"DewTime\",\n    short_names: [\"DewTime\"],\n    keywords: [\"dewtime\", \"mountaindew\", \"dew\", \"moutain\", \"time\"],\n    imageUrl: require(\"../../../assets/images/emotes/DewTime.gif\"),\n  },\n  {\n    name: \"EZ\",\n    short_names: [\"EZ\"],\n    keywords: [\"ez\"],\n    imageUrl: require(\"../../../assets/images/emotes/EZ.png\"),\n  },\n  {\n    name: \"F\",\n    short_names: [\"F\"],\n    keywords: [\"f\"],\n    imageUrl: require(\"../../../assets/images/emotes/F.gif\"),\n  },\n  {\n    name: \"FeelsDankMan\",\n    short_names: [\"FeelsDankMan\"],\n    keywords: [\"feelsdankman\", \"feels\", \"feel\", \"dank\", \"man\"],\n    imageUrl: require(\"../../../assets/images/emotes/FeelsDankMan.png\"),\n  },\n  {\n    name: \"FeelsOkayMan\",\n    short_names: [\"FeelsOkayMan\"],\n    keywords: [\"feelsokayman\", \"feels\", \"feel\", \"okay\", \"man\"],\n    imageUrl: require(\"../../../assets/images/emotes/FeelsOkayMan.png\"),\n  },\n  {\n    name: \"FeelsStrongMan\",\n    short_names: [\"FeelsStrongMan\"],\n    keywords: [\"feelsstrongman\", \"feels\", \"feel\", \"strong\", \"man\"],\n    imageUrl: require(\"../../../assets/images/emotes/FeelsStrongMan.png\"),\n  },\n  {\n    name: \"FeelsWeirdMan\",\n    short_names: [\"FeelsWeirdMan\"],\n    keywords: [\"feelsweirdman\", \"feels\", \"feel\", \"weird\", \"man\"],\n    imageUrl: require(\"../../../assets/images/emotes/FeelsWeirdMan.png\"),\n  },\n  {\n    name: \"gachiHYPER\",\n    short_names: [\"gachiHYPER\"],\n    keywords: [\"gachihyper\", \"gachi\", \"hyper\", \"coomer\"],\n    imageUrl: require(\"../../../assets/images/emotes/gachiHYPER.gif\"),\n  },\n  {\n    name: \"GRUG\",\n    short_names: [\"GRUG\"],\n    keywords: [\"grug\"],\n    imageUrl: require(\"../../../assets/images/emotes/GRUG.png\"),\n  },\n  {\n    name: \"GuitarTime\",\n    short_names: [\"GuitarTime\"],\n    keywords: [\"guitartime\", \"guitar\", \"time\"],\n    imageUrl: require(\"../../../assets/images/emotes/GuitarTime.gif\"),\n  },\n  {\n    name: \"HACKERMANS\",\n    short_names: [\"HACKERMANS\"],\n    keywords: [\n      \"hackermans\",\n      \"hacker\",\n      \"keyboard\",\n      \"typing\",\n      \"computer\",\n      \"coding\",\n      \"coder\",\n      \"code\",\n      \"virus\",\n      \"malware\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/HACKERMANS.gif\"),\n  },\n  {\n    name: \"HandsUp\",\n    short_names: [\"HandsUp\"],\n    keywords: [\"handsup\", \"hands\", \"up\", \"hand\"],\n    imageUrl: require(\"../../../assets/images/emotes/HandsUp.png\"),\n  },\n  {\n    name: \"hasanHyperSmash\",\n    short_names: [\"hasanHyperSmash\"],\n    keywords: [\n      \"hasanhypersmash\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"hyper\",\n      \"smash\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/hasanHyperSmash.gif\"),\n  },\n  {\n    name: \"hasanSmash\",\n    short_names: [\"hasanSmash\"],\n    keywords: [\"hasansmash\", \"hasan\", \"hasanabi\", \"hasanpiker\", \"smash\"],\n    imageUrl: require(\"../../../assets/images/emotes/hasanSmash.gif\"),\n  },\n  {\n    name: \"HasanWalk\",\n    short_names: [\"HasanWalk\"],\n    keywords: [\n      \"hasanwalk\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"walk\",\n      \"walking\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/HasanWalk.gif\"),\n  },\n  {\n    name: \"hasHyperJAM\",\n    short_names: [\"hasHyperJAM\"],\n    keywords: [\n      \"hashyperjam\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"hyper\",\n      \"jam\",\n      \"jamjam\",\n      \"jammies\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/hasHyperJAM.gif\"),\n  },\n  {\n    name: \"hasPls\",\n    short_names: [\"hasPls\"],\n    keywords: [\n      \"haspls\",\n      \"please\",\n      \"pls\",\n      \"plz\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/hasPls.gif\"),\n  },\n  {\n    name: \"hasRock\",\n    short_names: [\"hasRock\"],\n    keywords: [\n      \"hasrock\",\n      \"spongebob\",\n      \"patrick\",\n      \"hasan\",\n      \"hasanabi\",\n      \"hasanpiker\",\n      \"rock\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/hasRock.gif\"),\n  },\n  {\n    name: \"hasTasty\",\n    short_names: [\"hasTasty\"],\n    keywords: [\n      \"hastasty\",\n      \"hasan\",\n      \"tasty\",\n      \"yum\",\n      \"yummy\",\n      \"hasanabi\",\n      \"hasanpiker\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/hasTasty.gif\"),\n  },\n  {\n    name: \"HYPERCLAP\",\n    short_names: [\"HYPERCLAP\"],\n    keywords: [\"hyperclap\", \"hyper\", \"clap\"],\n    imageUrl: require(\"../../../assets/images/emotes/HYPERCLAP.gif\"),\n  },\n  {\n    name: \"hyperHammer\",\n    short_names: [\"hyperHammer\"],\n    keywords: [\"hyperhammer\", \"hyper\", \"hammer\"],\n    imageUrl: require(\"../../../assets/images/emotes/hyperHammer.gif\"),\n  },\n  {\n    name: \"HYPERITALIANHANDS\",\n    short_names: [\"HYPERITALIANHANDS\"],\n    keywords: [\n      \"hyperitalianhands\",\n      \"hyper\",\n      \"italian\",\n      \"hands\",\n      \"hand\",\n      \"italy\",\n      \"ovahere\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/HYPERITALIANHANDS.gif\"),\n  },\n  {\n    name: \"HYPERPOGGER\",\n    short_names: [\"HYPERPOGGER\"],\n    keywords: [\"hyperpogger\", \"hyper\", \"pog\", \"pogger\", \"poggies\"],\n    imageUrl: require(\"../../../assets/images/emotes/HYPERPOGGER.gif\"),\n  },\n  {\n    name: \"HYPERPOGO\",\n    short_names: [\"HYPERPOGO\"],\n    keywords: [\n      \"hyperpogo\",\n      \"hyper\",\n      \"pog\",\n      \"pogo\",\n      \"azan\",\n      \"poggies\",\n      \"poggers\",\n      \"pogger\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/HYPERPOGO.gif\"),\n  },\n  {\n    name: \"ItalianHands\",\n    short_names: [\"ItalianHands\"],\n    keywords: [\"italianhands\", \"ovahere\", \"italian\", \"hands\", \"hand\", \"italy\"],\n    imageUrl: require(\"../../../assets/images/emotes/ItalianHands.gif\"),\n  },\n  {\n    name: \"Jammies\",\n    short_names: [\"Jammies\"],\n    keywords: [\"jammies\", \"jam\", \"jamjam\", \"dance\", \"vibe\"],\n    imageUrl: require(\"../../../assets/images/emotes/Jammies.gif\"),\n  },\n  {\n    name: \"KEKebab\",\n    short_names: [\"KEKebab\"],\n    keywords: [\n      \"kekebab\",\n      \"kek\",\n      \"kebab\",\n      \"omegalul\",\n      \"lol\",\n      \"lmao\",\n      \"haha\",\n      \"ha\",\n      \"ja\",\n      \"jaja\",\n      \"kekw\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/KEKebab.png\"),\n  },\n  {\n    name: \"KEKW\",\n    short_names: [\"KEKW\"],\n    keywords: [\n      \"kekw\",\n      \"kek\",\n      \"omegalul\",\n      \"lol\",\n      \"lmao\",\n      \"haha\",\n      \"ha\",\n      \"ja\",\n      \"jaja\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/KEKW.png\"),\n  },\n  {\n    name: \"KEKWait\",\n    short_names: [\"KEKWait\"],\n    keywords: [\n      \"kekwait\",\n      \"lul\",\n      \"omegalul\",\n      \"lol\",\n      \"lmao\",\n      \"wait\",\n      \"kek\",\n      \"ha\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/KEKWait.png\"),\n  },\n  {\n    name: \"KKapitalist\",\n    short_names: [\"KKapitalist\"],\n    keywords: [\"kkapitalist\", \"kapitalist\", \"capitalist\", \"business\"],\n    imageUrl: require(\"../../../assets/images/emotes/KKapitalist.png\"),\n  },\n  {\n    name: \"KKomrade\",\n    short_names: [\"KKomrade\"],\n    keywords: [\"kkomrade\", \"komrade\", \"comrade\"],\n    imageUrl: require(\"../../../assets/images/emotes/KKomrade.png\"),\n  },\n  {\n    name: \"KKonaW\",\n    short_names: [\"KKonaW\"],\n    keywords: [\"kkonaw\"],\n    imageUrl: require(\"../../../assets/images/emotes/KKonaW.png\"),\n  },\n  {\n    name: \"KKop\",\n    short_names: [\"KKop\"],\n    keywords: [\n      \"kkop\",\n      \"kop\",\n      \"kops\",\n      \"cop\",\n      \"cops\",\n      \"police\",\n      \"acab\",\n      \"blueline\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/KKop.png\"),\n  },\n  {\n    name: \"LULW\",\n    short_names: [\"LULW\"],\n    keywords: [\"lulw\", \"lul\", \"lol\", \"lmao\", \"omegalul\"],\n    imageUrl: require(\"../../../assets/images/emotes/LULW.png\"),\n  },\n  {\n    name: \"lulWut\",\n    short_names: [\"lulWut\"],\n    keywords: [\n      \"lulwut\",\n      \"lul\",\n      \"lmao\",\n      \"lol\",\n      \"wut\",\n      \"what\",\n      \"wat\",\n      \"wtf\",\n      \"omegalul\",\n      \"ha\",\n      \"haha\",\n      \"ja\",\n      \"jaja\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/lulWut.png\"),\n  },\n  {\n    name: \"MALARKEY\",\n    short_names: [\"MALARKEY\"],\n    keywords: [\n      \"malarkey\",\n      \"ridinwithbiden\",\n      \"icecream\",\n      \"joe\",\n      \"biden\",\n      \"joebiden\",\n      \"46\",\n      \"president\",\n      \"laser\",\n      \"potus\",\n      \"democrat\",\n      \"us\",\n      \"usa\",\n      \"america\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/MALARKEY.gif\"),\n  },\n  {\n    name: \"MmmHmm\",\n    short_names: [\"MmmHmm\"],\n    keywords: [\n      \"mmmhmm\",\n      \"mmm\",\n      \"mmmm\",\n      \"hmm\",\n      \"hmmm\",\n      \"mmmmhmmm\",\n      \"mmmmhmm\",\n      \"mmmhmmm\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/MmmHmm.gif\"),\n  },\n  {\n    name: \"modCheck\",\n    short_names: [\"modCheck\"],\n    keywords: [\n      \"modcheck\",\n      \"mods\",\n      \"mod\",\n      \"check\",\n      \"spongebob\",\n      \"ask\",\n      \"askers\",\n      \"whoasked\",\n      \"where\",\n      \"look\",\n      \"fish\",\n      \"oooo\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/modCheck.gif\"),\n  },\n  {\n    name: \"ModTime\",\n    short_names: [\"ModTime\"],\n    keywords: [\"modtime\", \"mod\", \"mods\", \"time\"],\n    imageUrl: require(\"../../../assets/images/emotes/ModTime.gif\"),\n  },\n  {\n    name: \"monkaHmm\",\n    short_names: [\"monkaHmm\"],\n    keywords: [\"monkahmm\", \"monka\", \"hmmm\", \"hmm\"],\n    imageUrl: require(\"../../../assets/images/emotes/monkaHmm.png\"),\n  },\n  {\n    name: \"monkaStare\",\n    short_names: [\"monkaStare\"],\n    keywords: [\"monkastare\", \"stare\", \"monka\"],\n    imageUrl: require(\"../../../assets/images/emotes/monkaStare.png\"),\n  },\n  {\n    name: \"monkaSTEER\",\n    short_names: [\"monkaSTEER\"],\n    keywords: [\"monkasteer\", \"monka\", \"steer\", \"ridinwithbiden\"],\n    imageUrl: require(\"../../../assets/images/emotes/monkaSTEER.gif\"),\n  },\n  {\n    name: \"monkaW\",\n    short_names: [\"monkaW\"],\n    keywords: [\"monkaw\", \"monka\"],\n    imageUrl: require(\"../../../assets/images/emotes/monkaW.png\"),\n  },\n  {\n    name: \"neffHyperJAM\",\n    short_names: [\"neffHyperJAM\"],\n    keywords: [\n      \"neffhyperjam\",\n      \"hyperjam\",\n      \"jam\",\n      \"jammies\",\n      \"boppin\",\n      \"dj\",\n      \"jamjam\",\n      \"neff\",\n      \"will\",\n      \"willneff\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/neffHyperJAM.gif\"),\n  },\n  {\n    name: \"NODDERS\",\n    short_names: [\"NODDERS\"],\n    keywords: [\"nodders\", \"nod\", \"noddies\"],\n    imageUrl: require(\"../../../assets/images/emotes/NODDERS.gif\"),\n  },\n  {\n    name: \"NOPERS\",\n    short_names: [\"NOPERS\"],\n    keywords: [\"nopers\", \"no\", \"nope\"],\n    imageUrl: require(\"../../../assets/images/emotes/NOPERS.gif\"),\n  },\n  {\n    name: \"nymnCorn\",\n    short_names: [\"nymnCorn\"],\n    keywords: [\"nymncorn\", \"mmm\", \"corn\", \"nymn\"],\n    imageUrl: require(\"../../../assets/images/emotes/nymnCorn.gif\"),\n  },\n  {\n    name: \"OkayChamp\",\n    short_names: [\"OkayChamp\"],\n    keywords: [\"okaychamp\", \"okay\", \"champ\"],\n    imageUrl: require(\"../../../assets/images/emotes/OkayChamp.png\"),\n  },\n  {\n    name: \"OMEGALUL\",\n    short_names: [\"OMEGALUL\"],\n    keywords: [\"omegalul\", \"lol\", \"lmao\", \"lul\", \"ha\", \"haha\", \"ja\", \"jaja\"],\n    imageUrl: require(\"../../../assets/images/emotes/omegalul.png\"),\n  },\n  {\n    name: \"OOOO\",\n    short_names: [\"OOOO\"],\n    keywords: [\"oooo\", \"fish\"],\n    imageUrl: require(\"../../../assets/images/emotes/OOOO.gif\"),\n  },\n  {\n    name: \"PagChomp\",\n    short_names: [\"PagChomp\"],\n    keywords: [\"pagchomp\", \"pag\", \"chomp\"],\n    imageUrl: require(\"../../../assets/images/emotes/PagChomp.png\"),\n  },\n  {\n    name: \"PainsChamp\",\n    short_names: [\"PainsChamp\"],\n    keywords: [\"painschamp\", \"pains\", \"champ\", \"pain\"],\n    imageUrl: require(\"../../../assets/images/emotes/PainsChamp.png\"),\n  },\n  {\n    name: \"PauseChamp\",\n    short_names: [\"PauseChamp\"],\n    keywords: [\"pausechamp\", \"pause\", \"champ\"],\n    imageUrl: require(\"../../../assets/images/emotes/PauseChamp.png\"),\n  },\n  {\n    name: \"peepoArrive\",\n    short_names: [\"peepoArrive\"],\n    keywords: [\n      \"peepoarrive\",\n      \"peepo\",\n      \"pepo\",\n      \"arrive\",\n      \"here\",\n      \"welcome\",\n      \"hey\",\n      \"wave\",\n      \"hi\",\n      \"hello\",\n      \"howdy\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/peepoArrive.gif\"),\n  },\n  {\n    name: \"peepoBaba\",\n    short_names: [\"peepoBaba\"],\n    keywords: [\"peepobaba\", \"peepo\", \"pepo\", \"baba\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoBaba.png\"),\n  },\n  {\n    name: \"peepoBye\",\n    short_names: [\"peepoBye\"],\n    keywords: [\"peepobye\", \"peepo\", \"pepo\", \"bye\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoBye.gif\"),\n  },\n  {\n    name: \"peepoChat\",\n    short_names: [\"peepoChat\"],\n    keywords: [\"peepochat\", \"peepo\", \"pepo\", \"chat\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoChat.gif\"),\n  },\n  {\n    name: \"peepoCheer\",\n    short_names: [\"peepoCheer\"],\n    keywords: [\"peepocheer\", \"peepo\", \"pepo\", \"cheer\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoCheer.gif\"),\n  },\n  {\n    name: \"peepoClap\",\n    short_names: [\"peepoClap\"],\n    keywords: [\"peepoclap\", \"peepo\", \"pepo\", \"clap\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoClap.gif\"),\n  },\n  {\n    name: \"peepoD\",\n    short_names: [\"peepoD\"],\n    keywords: [\"peepod\", \"peepo\", \"pepo\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoD.gif\"),\n  },\n  {\n    name: \"peepoFat\",\n    short_names: [\"peepoFat\"],\n    keywords: [\"peepofat\", \"peepo\", \"pepo\", \"fat\", \"houngry\", \"hungry\", \"food\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoFat.png\"),\n  },\n  {\n    name: \"peepoGiggles\",\n    short_names: [\"peepoGiggles\"],\n    keywords: [\"peepogiggles\", \"pepo\", \"peepo\", \"giggle\", \"giggles\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoGiggles.gif\"),\n  },\n  {\n    name: \"peepoHey\",\n    short_names: [\"peepoHey\"],\n    keywords: [\n      \"peepohey\",\n      \"peepo\",\n      \"wave\",\n      \"pepo\",\n      \"hey\",\n      \"hi\",\n      \"hello\",\n      \"howdy\",\n      \"welcome\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/peepoHey.gif\"),\n  },\n  {\n    name: \"peepoJAMMER\",\n    short_names: [\"peepoJAMMER\"],\n    keywords: [\"peepojammer\", \"peepo\", \"pepo\", \"jam\", \"jammer\", \"jamjam\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoJAMMER.gif\"),\n  },\n  {\n    name: \"peepoKiss\",\n    short_names: [\"peepoKiss\"],\n    keywords: [\"peepokiss\", \"peepo\", \"pepo\", \"kiss\", \"love\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoKiss.png\"),\n  },\n  {\n    name: \"peepoLeave\",\n    short_names: [\"peepoLeave\"],\n    keywords: [\"peepoleave\", \"peepo\", \"pepo\", \"leave\", \"exit\", \"bye\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoLeave.gif\"),\n  },\n  {\n    name: \"peepoPog\",\n    short_names: [\"peepoPog\"],\n    keywords: [\"peepopog\", \"peepo\", \"pepo\", \"pog\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoPog.png\"),\n  },\n  {\n    name: \"peepoPogO\",\n    short_names: [\"peepoPogO\"],\n    keywords: [\"peepopogo\", \"peepo\", \"pepo\", \"pog\", \"pogo\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoPogO.png\"),\n  },\n  {\n    name: \"peepoRun\",\n    short_names: [\"peepoRun\"],\n    keywords: [\"peeporun\", \"peepo\", \"pepo\", \"run\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoRun.gif\"),\n  },\n  {\n    name: \"peepoShy\",\n    short_names: [\"peepoShy\"],\n    keywords: [\"peeposhy\", \"peepo\", \"pepo\", \"shy\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoShy.gif\"),\n  },\n  {\n    name: \"peepoSnow\",\n    short_names: [\"peepoSnow\"],\n    keywords: [\n      \"peeposnow\",\n      \"peepo\",\n      \"pepo\",\n      \"snow\",\n      \"christmas\",\n      \"cold\",\n      \"holidays\",\n    ],\n    imageUrl: require(\"../../../assets/images/emotes/peepoSnow.gif\"),\n  },\n  {\n    name: \"peepoT\",\n    short_names: [\"peepoT\"],\n    keywords: [\"peepot\", \"peepo\", \"pepo\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoT.gif\"),\n  },\n  {\n    name: \"peepoWeird\",\n    short_names: [\"peepoWeird\"],\n    keywords: [\"peepoweird\", \"peepo\", \"pepo\", \"weird\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepoWeird.png\"),\n  },\n  {\n    name: \"pepeCD\",\n    short_names: [\"pepeCD\"],\n    keywords: [\"pepecd\", \"pepe\", \"cd\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepeCD.gif\"),\n  },\n  {\n    name: \"PepegeHmm\",\n    short_names: [\"PepegeHmm\"],\n    keywords: [\"pepegehmm\", \"pepege\", \"pepe\", \"hmm\", \"hmmm\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepegeHmm.png\"),\n  },\n  {\n    name: \"PepeHands\",\n    short_names: [\"PepeHands\"],\n    keywords: [\"pepehands\", \"pepe\", \"hands\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepeHands.png\"),\n  },\n  {\n    name: \"pepeJAMJAM\",\n    short_names: [\"pepeJAMJAM\"],\n    keywords: [\"pepejamjam\", \"pepe\", \"jam\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepeJAMJAM.gif\"),\n  },\n  {\n    name: \"pepeJAM\",\n    short_names: [\"pepeJAM\"],\n    keywords: [\"pepeJAM\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepejam.gif\"),\n  },\n  {\n    name: \"PepeLa\",\n    short_names: [\"PepeLa\"],\n    keywords: [\"pepela\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepeLa.png\"),\n  },\n  {\n    name: \"PepeLaugh\",\n    short_names: [\"PepeLaugh\"],\n    keywords: [\"pepelaugh\", \"pepe\", \"laugh\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepeLaugh.gif\"),\n  },\n  {\n    name: \"pepeMeltdown\",\n    short_names: [\"pepeMeltdown\"],\n    keywords: [\"pepemeltdown\", \"pepe\", \"meltdown\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepeMeltdown.gif\"),\n  },\n  {\n    name: \"pepeP\",\n    short_names: [\"pepeP\"],\n    keywords: [\"pepep\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepeP.gif\"),\n  },\n  {\n    name: \"PepeS\",\n    short_names: [\"PepeS\"],\n    keywords: [\"pepes\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepeS.gif\"),\n  },\n  {\n    name: \"PepeSpit\",\n    short_names: [\"PepeSpit\"],\n    keywords: [\"pepespit\", \"pepe\", \"spit\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepeSpit.gif\"),\n  },\n  {\n    name: \"pepeW\",\n    short_names: [\"pepeW\"],\n    keywords: [\"pepew\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepeW.gif\"),\n  },\n  {\n    name: \"PepoG\",\n    short_names: [\"PepoG\"],\n    keywords: [\"pepog\", \"pepo\", \"peepo\"],\n    imageUrl: require(\"../../../assets/images/emotes/PepoG.png\"),\n  },\n  {\n    name: \"Pog\",\n    short_names: [\"Pog\"],\n    keywords: [\"pog\", \"poggies\", \"pogger\", \"poggers\"],\n    imageUrl: require(\"../../../assets/images/emotes/Pog.png\"),\n  },\n  {\n    name: \"POGGERS\",\n    short_names: [\"POGGERS\"],\n    keywords: [\"poggers\", \"pog\", \"poggies\", \"pogger\"],\n    imageUrl: require(\"../../../assets/images/emotes/POGGERS.png\"),\n  },\n  {\n    name: \"POGGIES\",\n    short_names: [\"POGGIES\"],\n    keywords: [\"poggies\", \"pog\", \"pogger\"],\n    imageUrl: require(\"../../../assets/images/emotes/POGGIES.png\"),\n  },\n  {\n    name: \"PogO\",\n    short_names: [\"PogO\"],\n    keywords: [\"pogo\", \"pog\", \"azan\"],\n    imageUrl: require(\"../../../assets/images/emotes/PogO.png\"),\n  },\n  {\n    name: \"PogU\",\n    short_names: [\"PogU\"],\n    keywords: [\"pogu\", \"pog\"],\n    imageUrl: require(\"../../../assets/images/emotes/PogU.png\"),\n  },\n  {\n    name: \"ppHop\",\n    short_names: [\"ppHop\"],\n    keywords: [\"pphop\", \"hop\"],\n    imageUrl: require(\"../../../assets/images/emotes/ppHop.gif\"),\n  },\n  {\n    name: \"ppOverheat\",\n    short_names: [\"ppOverheat\"],\n    keywords: [\"ppoverheat\", \"overheat\"],\n    imageUrl: require(\"../../../assets/images/emotes/ppOverheat.gif\"),\n  },\n  {\n    name: \"ppPoof\",\n    short_names: [\"ppPoof\"],\n    keywords: [\"pppoof\", \"poof\"],\n    imageUrl: require(\"../../../assets/images/emotes/ppPoof.gif\"),\n  },\n  {\n    name: \"Prayge\",\n    short_names: [\"Prayge\"],\n    keywords: [\"prayge\", \"pray\", \"god\"],\n    imageUrl: require(\"../../../assets/images/emotes/Prayge.png\"),\n  },\n  {\n    name: \"ratJAM\",\n    short_names: [\"ratJAM\"],\n    keywords: [\"ratjam\", \"rat\", \"jam\"],\n    imageUrl: require(\"../../../assets/images/emotes/ratJAM.gif\"),\n  },\n  {\n    name: \"REE\",\n    short_names: [\"REE\"],\n    keywords: [\"ree\", \"reeee\"],\n    imageUrl: require(\"../../../assets/images/emotes/REE.png\"),\n  },\n  {\n    name: \"SillyChamp\",\n    short_names: [\"SillyChamp\"],\n    keywords: [\"sillychamp\", \"silly\", \"champ\"],\n    imageUrl: require(\"../../../assets/images/emotes/SillyChamp.png\"),\n  },\n  {\n    name: \"SmokeTime\",\n    short_names: [\"SmokeTime\"],\n    keywords: [\"smoketime\", \"smoke\"],\n    imageUrl: require(\"../../../assets/images/emotes/SmokeTime.gif\"),\n  },\n  {\n    name: \"SWOONER\",\n    short_names: [\"SWOONER\"],\n    keywords: [\"swooner\"],\n    imageUrl: require(\"../../../assets/images/emotes/SWOONER.png\"),\n  },\n  {\n    name: \"TeaTime\",\n    short_names: [\"TeaTime\"],\n    keywords: [\"teatime\", \"tea\"],\n    imageUrl: require(\"../../../assets/images/emotes/TeaTime.gif\"),\n  },\n  {\n    name: \"TomatoTime\",\n    short_names: [\"TomatoTime\"],\n    keywords: [\"tomatotime\", \"tomato\"],\n    imageUrl: require(\"../../../assets/images/emotes/TomatoTime.gif\"),\n  },\n  {\n    name: \"unPOGGERS\",\n    short_names: [\"unPOGGERS\"],\n    keywords: [\"unpoggers\", \"poggers\", \"pog\", \"unpoggies\"],\n    imageUrl: require(\"../../../assets/images/emotes/unPOGGERS.png\"),\n  },\n  {\n    name: \"WeirdChamp\",\n    short_names: [\"WeirdChamp\"],\n    keywords: [\"weirdchamp\", \"weird\", \"champ\"],\n    imageUrl: require(\"../../../assets/images/emotes/WeirdChamp.png\"),\n  },\n  {\n    name: \"Weirdge\",\n    short_names: [\"Weirdge\"],\n    keywords: [\"weirdge\", \"weird\"],\n    imageUrl: require(\"../../../assets/images/emotes/Weirdge.png\"),\n  },\n  {\n    name: \"WhatChamp\",\n    short_names: [\"WhatChamp\"],\n    keywords: [\"whatchamp\", \"what\", \"champ\"],\n    imageUrl: require(\"../../../assets/images/emotes/WhatChamp.png\"),\n  },\n  {\n    name: \"WICKED\",\n    short_names: [\"WICKED\"],\n    keywords: [\"wicked\"],\n    imageUrl: require(\"../../../assets/images/emotes/WICKED.png\"),\n  },\n  {\n    name: \"widepeepoHappy\",\n    short_names: [\"widepeepoHappy\"],\n    keywords: [\"widepeepohappy\", \"peepohappy\", \"peepo\", \"happy\"],\n    imageUrl: require(\"../../../assets/images/emotes/widepeepoHappy.png\"),\n  },\n  {\n    name: \"widepeepoSad\",\n    short_names: [\"widepeepoSad\"],\n    keywords: [\"widepeeposad\", \"peeposad\", \"peepo\", \"sad\"],\n    imageUrl: require(\"../../../assets/images/emotes/widepeepoSad.png\"),\n  },\n  {\n    name: \"WineTime\",\n    short_names: [\"WineTime\"],\n    keywords: [\"winetime\", \"wine\"],\n    imageUrl: require(\"../../../assets/images/emotes/WineTime.gif\"),\n  },\n  {\n    name: \"YEP\",\n    short_names: [\"YEP\"],\n    keywords: [\"yep\"],\n    imageUrl: require(\"../../../assets/images/emotes/YEP.png\"),\n  },\n  {\n    name: \"ZZoomer\",\n    short_names: [\"ZZoomer\"],\n    keywords: [\"zzoomer\", \"zoomer\"],\n    imageUrl: require(\"../../../assets/images/emotes/ZZoomer.gif\"),\n  },\n  {\n    name: \"Doge3D\",\n    short_names: [\"Doge3D\"],\n    keywords: [\"doge3d\", \"doge\", \"3d\"],\n    imageUrl: require(\"../../../assets/images/emotes/doge3d.gif\"),\n  },\n  {\n    name: \"DogeCool\",\n    short_names: [\"DogeCool\"],\n    keywords: [\"dogecool\", \"doge\", \"cool\"],\n    imageUrl: require(\"../../../assets/images/emotes/dogecool.gif\"),\n  },\n  {\n    name: \"ThugPepe\",\n    short_names: [\"ThugPepe\"],\n    keywords: [\"dogehouse\", \"doge\", \"thugpepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/thugpepe.png\"),\n  },\n  {\n    name: \"blaze\",\n    short_names: [\"blaze\"],\n    keywords: [\"blaze\", \"yes\", \"snark\"],\n    imageUrl: require(\"../../../assets/images/emotes/blaze.png\"),\n  },\n  {\n    name: \"blobDance\",\n    short_names: [\"blobDance\"],\n    keywords: [\"blobDance\", \"vibe\"],\n    imageUrl: require(\"../../../assets/images/emotes/blobdance.gif\"),\n  },\n  {\n    name: \"PepePls\",\n    short_names: [\"PepePls\"],\n    keywords: [\"PepePls\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepepls.gif\"),\n  },\n  {\n    name: \"FeelsGoodMan\",\n    short_names: [\"FeelsGoodMan\"],\n    keywords: [\"FeelsGoodMan\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/feelsgoodman.png\"),\n  },\n  {\n    name: \"GitHub\",\n    short_names: [\"GitHub\"],\n    keywords: [\"app\", \"git\", \"github\"],\n    imageUrl: require(\"../../../assets/images/emotes/github.png\"),\n  },\n  {\n    name: \"Elixir\",\n    short_names: [\"Elixir\"],\n    keywords: [\"app\", \"elixir\"],\n    imageUrl: require(\"../../../assets/images/emotes/elixir.png\"),\n  },\n  {\n    name: \"NextJS\",\n    short_names: [\"NextJS\"],\n    keywords: [\"app\", \"next\", \"js\"],\n    imageUrl: require(\"../../../assets/images/emotes/nextjs.png\"),\n  },\n  {\n    name: \"React\",\n    short_names: [\"React\"],\n    keywords: [\"app\", \"react\", \"js\"],\n    imageUrl: require(\"../../../assets/images/emotes/react.png\"),\n  },\n  {\n    name: \"Electron\",\n    short_names: [\"Electron\"],\n    keywords: [\"app\", \"electron\"],\n    imageUrl: require(\"../../../assets/images/emotes/electron.png\"),\n  },\n  {\n    name: \"BeardGuyR\",\n    short_names: [\"BeardGuyR\"],\n    keywords: [\"meme\", \"beard\", \"guy\"],\n    imageUrl: require(\"../../../assets/images/emotes/beardguyright.png\"),\n  },\n  {\n    name: \"BeardGuyL\",\n    short_names: [\"BeardGuyL\"],\n    keywords: [\"meme\", \"beard\", \"guy\"],\n    imageUrl: require(\"../../../assets/images/emotes/beardguyleft.png\"),\n  },\n  {\n    name: \"MemeMan\",\n    short_names: [\"MemeMan\"],\n    keywords: [\"meme\", \"guy\"],\n    imageUrl: require(\"../../../assets/images/emotes/mememan.png\"),\n  },\n  {\n    name: \"NPC\",\n    short_names: [\"NPC\"],\n    keywords: [\"meme\", \"npc\", \"guy\"],\n    imageUrl: require(\"../../../assets/images/emotes/npc.png\"),\n  },\n  {\n    name: \"SmudgeCat\",\n    short_names: [\"SMudgeCat\"],\n    keywords: [\"meme\", \"cat\"],\n    imageUrl: require(\"../../../assets/images/emotes/smudgecat.png\"),\n  },\n  {\n    name: \"Stonks\",\n    short_names: [\"Stonks\"],\n    keywords: [\"meme\", \"crypto\", \"guy\"],\n    imageUrl: require(\"../../../assets/images/emotes/stonks.png\"),\n  },\n  {\n    name: \"Stinks\",\n    short_names: [\"Stinks\"],\n    keywords: [\"meme\", \"crypto\", \"guy\"],\n    imageUrl: require(\"../../../assets/images/emotes/stinks.png\"),\n  },\n  {\n    name: \"IWMD\",\n    short_names: [\"IWMD\"],\n    keywords: [\"meme\", \"dude\", \"wednesday\", \"frog\"],\n    imageUrl: require(\"../../../assets/images/emotes/wednesday.png\"),\n  },\n  {\n    name: \"PreSponge\",\n    short_names: [\"PreSponge\"],\n    keywords: [\"meme\", \"spongebob\"],\n    imageUrl: require(\"../../../assets/images/emotes/presponge.png\"),\n  },\n  {\n    name: \"sPonGeBoB\",\n    short_names: [\"sPonGeBoB\"],\n    keywords: [\"meme\", \"spongebob\"],\n    imageUrl: require(\"../../../assets/images/emotes/sPonGeBoB.png\"),\n  },\n  {\n    name: \"peepohappy\",\n    short_names: [\"peepohappy\"],\n    keywords: [\"peepohappy\", \"peepo\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepohappy.png\"),\n  },\n  {\n    name: \"peepohug\",\n    short_names: [\"peepohug\"],\n    keywords: [\"peepohug\", \"peepo\", \"pepe\"],\n    imageUrl: require(\"../../../assets/images/emotes/peepohug.png\"),\n  },\n  {\n    name: \"PurpleDogeHouse\",\n    short_names: [\"PurpleDogeHouse\"],\n    keywords: [\"purple\", \"dogehouse\", \"doge\"],\n    imageUrl: require(\"../../../assets/images/emotes/purpledogehouse.png\"),\n  },\n  {\n    name: \"widepeepoPussy\",\n    short_names: [\"widepeepoPussy\"],\n    keywords: [\"widepeepoPussy\", \"peepo\", \"wide\"],\n    imageUrl: require(\"../../../assets/images/emotes/widepeepoPussy.png\"),\n  },\n  {\n    name: \"RareParrot\",\n    short_names: [\"RareParrot\"],\n    keywords: [\"Rare\", \"Parrot\", \"party\"],\n    imageUrl: require(\"../../../assets/images/emotes/rareParrot.gif\"),\n  },\n  {\n    name: \"OrangeDogeHouse\",\n    short_names: [\"OrangeDogeHouse\"],\n    keywords: [\"orange\", \"dogehouse\", \"doge\"],\n    imageUrl: require(\"../../../assets/images/emotes/orangedogehouse.png\"),\n  },\n  {\n    name: \"CyanDogeHouse\",\n    short_names: [\"CyanDogeHouse\"],\n    keywords: [\"cyan\", \"dogehouse\", \"doge\"],\n    imageUrl: require(\"../../../assets/images/emotes/cyandogehouse.png\"),\n  },\n  {\n    name: \"AngryKermit\",\n    short_names: [\"AngryKermit\"],\n    keywords: [\"kermit\", \"angry\", \"ak47\"],\n    imageUrl: require(\"../../../assets/images/emotes/angrykermit.png\"),\n  },\n  {\n    name: \"DarthKermit\",\n    short_names: [\"DarthKermit\"],\n    keywords: [\"kermit\", \"star\", \"wars\", \"darth\"],\n    imageUrl: require(\"../../../assets/images/emotes/darthkermit.png\"),\n  },\n  {\n    name: \"Anime Girl Pepe\",\n    short_names: [\"AnimePepe\"],\n    keywords: [\"pepe\", \"anime\", \"girl\"],\n    imageUrl: require(\"../../../assets/images/emotes/pepeanimegirl.png\"),\n  },\n  {\n    name: \"TakeMyMoney\",\n    short_names: [\"TakeMyMoney\"],\n    keywords: [\"take\", \"my\", \"money\", \"shut\", \"up\"],\n    imageUrl: require(\"../../../assets/images/emotes/takemymoney.png\"),\n  },\n  {\n    name: \"awyeah\",\n    short_names: [\"awyeah\"],\n    keywords: [\"aw\", \"yeah\", \"awyeah\", \"dancing\"],\n    imageUrl: require(\"../../../assets/images/emotes/awyeah.gif\"),\n  },\n  {\n    name: \"ZeroTwoLul\",\n    short_names: [\"ZTlul\"],\n    keywords: [\"lul\", \"lol\", \"anime\", \"girl\", \"zerotwo\"],\n    imageUrl: require(\"../../../assets/images/emotes/zerotwolul.png\"),\n  },\n  {\n    name: \"ZeroTwoSmug\",\n    short_names: [\"ZTsmug\"],\n    keywords: [\"smug\", \"anime\", \"girl\", \"zerotwo\"],\n    imageUrl: require(\"../../../assets/images/emotes/zerotwosmug.png\"),\n  },\n  {\n    name: \"ZeroTwoThinking\",\n    short_names: [\"ZTthinking\"],\n    keywords: [\"thinking\", \"think\", \"anime\", \"girl\", \"zerotwo\"],\n    imageUrl: require(\"../../../assets/images/emotes/zerotwothinking.png\"),\n  },\n  {\n    name: \"PartyParrot\",\n    short_names: [\"PartyParrot\"],\n    keywords: [\"party\", \"high\", \"parrot\", \"fun\"],\n    imageUrl: require(\"../../../assets/images/emotes/PartyParrot.gif\"),\n  },\n  {\n    name: \"DogePls\",\n    short_names: [\"DogePls\"],\n    keywords: [\"doge\", \"please\", \"fun\", \"dance\"],\n    imageUrl: require(\"../../../assets/images/emotes/DogePls.gif\"),\n  },\n  {\n    name: \"catDance\",\n    short_names: [\"catDance\"],\n    keywords: [\"cat\", \"dance\", \"party\", \"fun\"],\n    imageUrl: require(\"../../../assets/images/emotes/catDance.gif\"),\n  },\n]; //.map((e) => ({ ...e, customCategory: \"Custom\", text: \"\", emoticons: [] }));\n\nexport const emoteMap: Record<string, ImageSourcePropType> = {};\n\ncustomEmojis.forEach((e) => {\n  emoteMap[e.name] = e.imageUrl;\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/EmotePicker.tsx",
    "content": "import React from \"react\";\nimport {\n  Image,\n  ImageSourcePropType,\n  StyleSheet,\n  Text,\n  TouchableOpacity,\n  View,\n  ViewProps,\n} from \"react-native\";\nimport { colors, paragraph, radius } from \"../../../constants/dogeStyle\";\nimport { customEmojis, emoteMap } from \"./EmoteData\";\nimport { ScrollView } from \"react-native-gesture-handler\";\n\nexport type EmotePickerProps = ViewProps & {\n  isNitro?: boolean;\n  onEmoteSelected: (emote: { name: string; src: ImageSourcePropType }) => void;\n};\n\nexport const EmotePicker: React.FC<EmotePickerProps> = ({\n  style,\n  isNitro = false,\n  onEmoteSelected,\n}) => {\n  return (\n    <ScrollView style={[styles.container, style]}>\n      {isNitro && (\n        <>\n          <View>\n            <Text style={styles.text}>DogeNitro exclusive</Text>\n            <View style={styles.emoteContainer}>\n              {customEmojis.slice(0, 16).map((e) => (\n                <TouchableOpacity\n                  key={e.name}\n                  style={{ height: 40, width: 40, padding: 5 }}\n                  onPress={() =>\n                    onEmoteSelected({ name: e.name, src: e.imageUrl })\n                  }\n                >\n                  <Image source={e.imageUrl} />\n                </TouchableOpacity>\n              ))}\n            </View>\n          </View>\n          <View\n            style={{\n              backgroundColor: colors.primary700,\n              height: 1,\n              marginVertical: 10,\n            }}\n          />\n        </>\n      )}\n      <View style={styles.emoteContainer}>\n        {customEmojis.map((e) => (\n          <TouchableOpacity\n            key={e.name}\n            style={{ height: 40, width: 40, padding: 5 }}\n            onPress={() => onEmoteSelected({ name: e.name, src: e.imageUrl })}\n          >\n            <Image source={e.imageUrl} />\n          </TouchableOpacity>\n        ))}\n      </View>\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    borderRadius: radius.m,\n    backgroundColor: colors.primary700,\n    flex: 1,\n    paddingHorizontal: 0,\n    paddingVertical: 10,\n  },\n  emoteContainer: {\n    flexDirection: \"row\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-evenly\",\n  },\n  text: {\n    ...paragraph,\n    color: colors.primary300,\n    paddingHorizontal: 20,\n    marginTop: 10,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/RoomChat.tsx",
    "content": "import { Room, RoomUser } from \"@dogehouse/kebab\";\nimport { useKeyboard } from \"@react-native-community/hooks\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport React, { MutableRefObject, useEffect, useState } from \"react\";\nimport { Keyboard, View, ViewStyle } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport BottomSheet from \"reanimated-bottom-sheet\";\nimport { colors } from \"../../../constants/dogeStyle\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { useCurrentRoomInfo } from \"../../../shared-hooks/useCurrentRoomInfo\";\nimport { EmotePicker } from \"./EmotePicker\";\nimport { RoomChatControls } from \"./RoomChatControls\";\nimport { RoomChatInput } from \"./RoomChatInput\";\nimport { RoomChatList } from \"./RoomChatList\";\nimport { RoomChatMessage, useRoomChatStore } from \"./useRoomChatStore\";\n\ninterface ChatProps {\n  room: Room;\n  users: RoomUser[];\n  style: ViewStyle;\n  wrapperRef: MutableRefObject<BottomSheet>;\n}\n\nexport const RoomChat: React.FC<ChatProps> = ({\n  users,\n  room,\n  style,\n  wrapperRef,\n}) => {\n  const inset = useSafeAreaInsets();\n  const [emoteOpen, setEmoteOpen] = useState(false);\n  const { message, setMessage } = useRoomChatStore();\n  const keyboard = useKeyboard();\n  const navigation = useNavigation();\n  const { canSpeak } = useCurrentRoomInfo();\n  const conn = useConn();\n  const me = users.find((u) => u.id === conn.user.id);\n  useEffect(() => {\n    Keyboard.addListener(\"keyboardWillShow\", _keyboardDidShow);\n    Keyboard.addListener(\"keyboardWillHide\", _keyboardDidHide);\n\n    // cleanup function\n    return () => {\n      Keyboard.removeListener(\"keyboardWillShow\", _keyboardDidShow);\n      Keyboard.removeListener(\"keyboardWillHide\", _keyboardDidHide);\n    };\n  }, []);\n  const [keyboardStatus, setKeyboardStatus] = useState(false);\n  const _keyboardDidShow = (event) => {\n    Keyboard.scheduleLayoutAnimation(event);\n    setKeyboardStatus(true);\n  };\n  const _keyboardDidHide = (event) => {\n    Keyboard.scheduleLayoutAnimation(event);\n    setKeyboardStatus(false);\n  };\n  return (\n    <View\n      style={[\n        style,\n        {\n          backgroundColor: colors.primary900,\n          justifyContent: \"flex-end\",\n          paddingBottom: 10 + inset.bottom,\n          // flex: 1,\n        },\n      ]}\n    >\n      <RoomChatControls\n        room={room}\n        amISpeaker={canSpeak}\n        amIAskingForSpeak={me.roomPermissions?.askedToSpeak}\n      />\n      <RoomChatList\n        room={room}\n        onUsernamePress={(userId: string, message?: RoomChatMessage) => {\n          navigation.navigate(\"RoomUserPreview\", { userId, message });\n          wrapperRef.current.snapTo(1);\n        }}\n      />\n      {emoteOpen && (\n        <EmotePicker\n          style={{\n            position: \"absolute\",\n            // display: emoteOpen ? \"flex\" : \"none\",\n            bottom: inset.bottom + 60,\n            height: keyboardStatus ? \"25%\" : \"50%\",\n            left: 25,\n            right: 25,\n            marginBottom: keyboardStatus\n              ? keyboard.keyboardHeight - inset.bottom\n              : 0,\n          }}\n          isNitro={false}\n          onEmoteSelected={(emote) => {\n            setMessage(message + \":\" + emote.name + \":\");\n            setEmoteOpen(false);\n          }}\n        />\n      )}\n      <RoomChatInput\n        users={users}\n        onEmotePress={() => setEmoteOpen(!emoteOpen)}\n        style={{\n          paddingHorizontal: 25,\n          marginBottom: keyboardStatus\n            ? keyboard.keyboardHeight - inset.bottom\n            : 0,\n        }}\n      />\n    </View>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/RoomChatControls.tsx",
    "content": "import { Room, wrap } from \"@dogehouse/kebab\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport React, { useState } from \"react\";\nimport { Image, StyleSheet, TouchableOpacity, View } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { colors, radius } from \"../../../constants/dogeStyle\";\nimport { useMuteStore } from \"../../../global-stores/useMuteStore\";\nimport { InCallManagerSetSpeakerOn } from \"../../../lib/inCallManagerCenter\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { useSetMute } from \"../../../shared-hooks/useSetMute\";\n\ninterface RoomChatControlsProps {\n  room: Room;\n  amISpeaker: boolean;\n  amIAskingForSpeak: boolean;\n}\n\nconst micOff = require(\"../../../assets/images/SolidMicrophoneOff.png\");\nconst micOn = require(\"../../../assets/images/sm-solid-microphone.png\");\nconst askSpeak = require(\"../../../assets/images/sm-solid-megaphone.png\");\nconst volumeHigh = require(\"../../../assets/images/ios-volume-high.png\");\nconst invite = require(\"../../../assets/images/md-person-add.png\");\n\nexport const RoomChatControls: React.FC<RoomChatControlsProps> = ({\n  room,\n  amISpeaker,\n  amIAskingForSpeak,\n}) => {\n  const conn = useConn();\n  const { muted } = useMuteStore();\n  const setMute = useSetMute();\n  const inset = useSafeAreaInsets();\n  const navigation = useNavigation();\n  const [speakerOn, setSpeakerOn] = useState(false);\n  InCallManagerSetSpeakerOn(speakerOn);\n\n  let onPress;\n  if (amISpeaker) {\n    onPress = () => setMute(!muted);\n  } else {\n    if (amIAskingForSpeak) {\n      onPress = () => {};\n    } else {\n      onPress = () => wrap(conn).mutation.askToSpeak();\n    }\n  }\n\n  return (\n    <View\n      style={{\n        backgroundColor: colors.primary800,\n        paddingBottom: inset.bottom + 20,\n      }}\n    >\n      <View style={styles.toggle} />\n      <View style={[styles.controlsContainer]}>\n        <TouchableOpacity style={styles.micControl} onPress={onPress}>\n          {amISpeaker && (\n            <Image\n              source={muted ? micOff : micOn}\n              style={{ tintColor: colors.text, width: 20, height: 20 }}\n            />\n          )}\n          {!amISpeaker && (\n            <Image\n              source={askSpeak}\n              style={{\n                tintColor: colors.text,\n                width: 20,\n                height: 20,\n              }}\n            />\n          )}\n        </TouchableOpacity>\n        <View style={{ flexGrow: 1 }} />\n        <TouchableOpacity\n          style={[\n            styles.soundControl,\n            speakerOn && { backgroundColor: colors.primary600 },\n          ]}\n          onPress={() => setSpeakerOn(!speakerOn)}\n        >\n          <Image\n            source={volumeHigh}\n            style={{ tintColor: colors.text, width: 20, height: 17.5 }}\n          />\n        </TouchableOpacity>\n        <TouchableOpacity\n          style={styles.inviteControl}\n          onPress={() => navigation.navigate(\"RoomInvitation\", { room: room })}\n        >\n          <Image source={invite} style={{ width: 20, height: 16 }} />\n        </TouchableOpacity>\n      </View>\n    </View>\n  );\n};\n\nconst styles = StyleSheet.create({\n  toggle: {\n    width: 40,\n    height: 4,\n    borderRadius: 2,\n    backgroundColor: colors.primary300,\n    alignSelf: \"center\",\n    marginTop: 15,\n    marginBottom: 10,\n  },\n  controlsContainer: {\n    flexDirection: \"row\",\n    paddingHorizontal: 25,\n  },\n  micControl: {\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    backgroundColor: colors.primary700,\n    borderRadius: radius.m,\n    height: 50,\n    width: 80,\n  },\n  soundControl: {\n    height: 50,\n    width: 50,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    backgroundColor: colors.primary700,\n    borderRadius: 25,\n    marginRight: 15,\n  },\n  inviteControl: {\n    height: 50,\n    width: 50,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    backgroundColor: colors.primary700,\n    borderRadius: 25,\n    paddingRight: 2.5,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/RoomChatInput.tsx",
    "content": "import { RoomUser } from \"@dogehouse/kebab\";\nimport React, { useRef, useState } from \"react\";\nimport {\n  Image,\n  TextInput,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\";\nimport { colors, radius } from \"../../../constants/dogeStyle\";\nimport { createChatMessage } from \"../../../lib/createChatMessage\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { RoomChatMentions } from \"./RoomChatMentions\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\n\ninterface ChatInputProps {\n  style: ViewStyle;\n  users: RoomUser[];\n  onEmotePress: () => void;\n}\n\nexport const RoomChatInput: React.FC<ChatInputProps> = ({\n  users,\n  onEmotePress,\n  style,\n}) => {\n  const { message, setMessage } = useRoomChatStore();\n  const {\n    setQueriedUsernames,\n    queriedUsernames,\n    mentions,\n    setMentions,\n    activeUsername,\n    setActiveUsername,\n  } = useRoomChatMentionStore();\n  const conn = useConn();\n  const me = conn.user;\n  const [isEmoji, setIsEmoji] = useState<boolean>(false);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const [lastMessageTimestamp, setLastMessageTimestamp] = useState<number>(0);\n\n  let position = 0;\n\n  const handleSubmit = () => {\n    if (!me) return;\n    if (me.id in useRoomChatStore.getState().bannedUserIdMap) {\n      // showErrorToast(t(\"modules.roomChat.bannedAlert\"));\n      return;\n    }\n\n    if (Date.now() - lastMessageTimestamp <= 1000) {\n      // showErrorToast(t(\"modules.roomChat.waitAlert\"));\n\n      return;\n    }\n    const tmp = message;\n    const messageData = createChatMessage(tmp, mentions, users);\n    // dont empty the input, if no tokens\n    if (!messageData.tokens.length) return;\n    setMessage(\"\");\n    if (\n      !message ||\n      !message.trim() ||\n      !message.replace(/[\\u200B-\\u200D\\uFEFF]/g, \"\")\n    ) {\n      return;\n    }\n\n    conn.send(\"send_room_chat_msg\", messageData);\n    setQueriedUsernames([]);\n\n    setLastMessageTimestamp(Date.now());\n\n    setMessage(\"\");\n  };\n\n  return (\n    <View style={style}>\n      <RoomChatMentions users={users} />\n      <View\n        style={{\n          flexDirection: \"row\",\n          height: 40,\n          backgroundColor: colors.primary700,\n          paddingHorizontal: 16,\n          borderRadius: radius.m,\n        }}\n      >\n        <View style={{ flex: 1 }}>\n          <TextInput\n            placeholder={\"Send a message\"}\n            placeholderTextColor={colors.primary300}\n            autoCorrect={false}\n            style={{\n              height: 40,\n              color: colors.text,\n            }}\n            value={message}\n            onSubmitEditing={handleSubmit}\n            onChangeText={(value) => setMessage(value)}\n          />\n        </View>\n\n        <TouchableOpacity\n          style={{ alignSelf: \"center\", flexShrink: 0, marginLeft: 10 }}\n          onPress={onEmotePress}\n        >\n          <Image source={require(\"../../../assets/images/emoji-icon.png\")} />\n        </TouchableOpacity>\n      </View>\n    </View>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/RoomChatList.tsx",
    "content": "import { Room } from \"@dogehouse/kebab\";\nimport React, { useCallback } from \"react\";\nimport { FlatList, View } from \"react-native\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { useCurrentRoomInfo } from \"../../../shared-hooks/useCurrentRoomInfo\";\nimport { RoomMessage } from \"./RoomMessage\";\nimport { RoomChatMessage, useRoomChatStore } from \"./useRoomChatStore\";\n\ninterface ChatListProps {\n  room: Room;\n  onUsernamePress: (userId: string, message?: RoomChatMessage) => void;\n}\n\nexport const RoomChatList: React.FC<ChatListProps> = ({\n  room,\n  onUsernamePress,\n}) => {\n  const messages = useRoomChatStore((s) => s.messages);\n\n  const me = useConn().user;\n  const { isMod: iAmMod, isCreator: iAmCreator } = useCurrentRoomInfo();\n\n  const renderMessage = useCallback(\n    ({ item }) => {\n      return (\n        <RoomMessage\n          m={item}\n          me={me}\n          iAmMod={iAmMod}\n          iAmCreator={iAmCreator}\n          creatorId={room.creatorId}\n          onUsernamePress={onUsernamePress}\n        />\n      );\n    },\n    [iAmCreator, iAmMod, me, onUsernamePress, room.creatorId]\n  );\n\n  const keyExtractor = useCallback((item) => item.id, []);\n\n  return (\n    <View\n      style={{\n        padding: 5,\n        paddingHorizontal: 25,\n        flexGrow: 1,\n      }}\n    >\n      <FlatList\n        inverted\n        style={{ flex: 1, marginBottom: 10, paddingBottom: 10 }}\n        contentContainerStyle={{\n          flexGrow: 1,\n          justifyContent: \"flex-start\",\n        }}\n        data={messages}\n        renderItem={renderMessage}\n        keyExtractor={keyExtractor}\n      />\n    </View>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/RoomChatMentions.tsx",
    "content": "import { BaseUser, RoomUser } from \"@dogehouse/kebab\";\nimport React, { useEffect } from \"react\";\nimport { useConn } from \"../../../shared-hooks/useConn\";\nimport { SingleUserAvatar } from \"../../../components/avatars/SingleUserAvatar\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\nimport { useRoomChatStore } from \"./useRoomChatStore\";\nimport { View, Text, TouchableOpacity } from \"react-native\";\nimport { colors, h4, paragraph, radius } from \"../../../constants/dogeStyle\";\n\ninterface RoomChatMentionsProps {\n  users: RoomUser[];\n}\n\nexport const RoomChatMentions: React.FC<RoomChatMentionsProps> = ({\n  users,\n}) => {\n  const me = useConn().user;\n\n  const { message, setMessage } = useRoomChatStore();\n\n  const {\n    activeUsername,\n    setActiveUsername,\n    queriedUsernames,\n    setQueriedUsernames,\n    mentions,\n    setMentions,\n  } = useRoomChatMentionStore();\n\n  function addMention(m: BaseUser) {\n    setMentions([...mentions, m]);\n    setMessage(\n      message.substring(0, message.lastIndexOf(\"@\") + 1) + m.username + \" \"\n    );\n    setQueriedUsernames([]);\n  }\n\n  useEffect(() => {\n    // regex to match mention patterns\n    const mentionMatches = message.match(/^(?!.*\\bRT\\b)(?:.+\\s)?#?@\\w+/i);\n\n    // query usernames for matched patterns\n    if (mentionMatches && me) {\n      const mentionsList = mentionMatches[0].replace(/@|#/g, \"\").split(\" \");\n      const useMention = mentionsList[mentionsList.length - 1];\n\n      // hide usernames list if user continues typing without selecting\n      if (message[message.lastIndexOf(useMention) + useMention.length]) {\n        setQueriedUsernames([]);\n      } else {\n        const usernameMatches = users.filter(\n          ({ id, username, displayName }) =>\n            (username?.toLowerCase().includes(useMention?.toLowerCase()) ||\n              displayName?.toLowerCase().includes(useMention?.toLowerCase())) &&\n            !mentions.find((m: BaseUser) => m.id === id) &&\n            me.id !== id\n        );\n\n        const firstFive = usernameMatches.slice(0, 5);\n        setQueriedUsernames(firstFive);\n        if (firstFive.length) setActiveUsername(firstFive[0].id);\n      }\n    } else {\n      // Hide mentions when message is sent\n      setQueriedUsernames([]);\n    }\n\n    // Remove mention if user deleted text\n    setMentions(\n      mentions.filter((u) => {\n        return message.toLowerCase().indexOf(u.username?.toLowerCase()) !== -1;\n      })\n    );\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [message]);\n  if (queriedUsernames.length) {\n    return (\n      <View\n        style={{\n          position: \"absolute\",\n          backgroundColor: colors.primary700,\n          width: \"100%\",\n          alignSelf: \"center\",\n          bottom: 34,\n          paddingBottom: 5,\n          borderTopLeftRadius: radius.m,\n          borderTopRightRadius: radius.m,\n        }}\n      >\n        {queriedUsernames.map((m) => (\n          <TouchableOpacity\n            key={m.id}\n            style={{\n              flexDirection: \"row\",\n              alignItems: \"center\",\n              paddingHorizontal: 15,\n              paddingVertical: 15,\n            }}\n            onPress={() => addMention(m)}\n          >\n            <SingleUserAvatar src={{ uri: m.avatarUrl }} size={\"xxs\"} />\n            <Text style={{ ...paragraph, marginLeft: 10 }}>\n              {m.displayName}\n              {m.displayName !== m.username ? ` (${m.username})` : \"\"}\n            </Text>\n          </TouchableOpacity>\n        ))}\n      </View>\n    );\n  }\n  return <></>;\n};\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/RoomMessage.tsx",
    "content": "import { User } from \"@dogehouse/kebab\";\nimport React from \"react\";\nimport { Image, Text, View } from \"react-native\";\nimport {\n  colors,\n  fontSize,\n  radius,\n  small,\n  smallBold,\n} from \"../../../constants/dogeStyle\";\nimport { emoteMap } from \"./EmoteData\";\nimport { RoomChatMessage } from \"./useRoomChatStore\";\n\nconst RoomMessageMemoized: React.FC<{\n  m: RoomChatMessage;\n  me: User;\n  iAmCreator: boolean;\n  iAmMod: boolean;\n  creatorId: string;\n  onUsernamePress: (userId: string, message?: RoomChatMessage) => void;\n}> = ({ m, me, iAmCreator, iAmMod, creatorId, onUsernamePress }) => {\n  console.log(m);\n  return (\n    <View\n      key={m.id}\n      style={[\n        { marginTop: 8 },\n        m.isWhisper\n          ? {\n              backgroundColor: colors.primary700,\n              borderRadius: radius.s,\n              paddingHorizontal: 5,\n            }\n          : {},\n      ]}\n    >\n      {m.isWhisper && (\n        <Text\n          style={{\n            ...small,\n            fontSize: fontSize.xs,\n            color: colors.primary300,\n          }}\n        >\n          Whisper\n        </Text>\n      )}\n      <Text>\n        <Text\n          style={{\n            ...smallBold,\n            color: m.color,\n            marginHorizontal: 5,\n            textAlignVertical: \"center\",\n          }}\n          onPress={() => {\n            onUsernamePress(\n              m.userId,\n              (me?.id === m.userId ||\n                iAmCreator ||\n                (iAmMod && creatorId !== m.userId)) &&\n                !m.deleted\n                ? m\n                : undefined\n            );\n          }}\n        >\n          {m.username}:{\" \"}\n        </Text>\n\n        <Text style={{ ...small, lineHeight: undefined }}>\n          {m.deleted ? (\n            <Text>\n              [message {m.deleterId === m.userId ? \"retracted\" : \"deleted\"}]\n            </Text>\n          ) : (\n            m.tokens.map(({ t: token, v }, i) => {\n              switch (token) {\n                case \"text\":\n                  return <Text key={i}>{v} </Text>;\n                case \"emote\":\n                  return emoteMap[v] ? (\n                    m.tokens.find((t) => t.t === \"text\") !== undefined ? (\n                      <Image\n                        key={i}\n                        source={emoteMap[v]}\n                        style={{\n                          height: 20,\n                          width: 20,\n                        }}\n                      />\n                    ) : (\n                      <Image key={i} source={emoteMap[v]} />\n                    )\n                  ) : (\n                    \":\" + v + \":\"\n                  );\n\n                case \"mention\": {\n                  if (!m.isWhisper) {\n                    return (\n                      <Text\n                        key={i}\n                        style={{\n                          color: colors.primary300,\n                        }}\n                      >\n                        @{v}{\" \"}\n                      </Text>\n                    );\n                  }\n                  return null;\n                }\n\n                case \"link\":\n                  return (\n                    <Text key={i}>\n                      {v}\n                      {/* {normalizeUrl(v, { stripProtocol: true })}{\" \"} */}\n                    </Text>\n                  );\n                case \"block\":\n                  return <Text key={i}>{v}</Text>;\n                default:\n                  return null;\n              }\n            })\n          )}\n        </Text>\n      </Text>\n    </View>\n  );\n};\n\nexport const RoomMessage = React.memo(RoomMessageMemoized, (prev, next) => {\n  return prev.m.id === next.m.id && prev.m.deleted === next.m.deleted;\n});\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/useRoomChatMentionStore.ts",
    "content": "import { BaseUser } from \"@dogehouse/kebab\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useSoundEffectStore } from \"../../sound-effect/useSoundEffectStore\";\n\nexport const useRoomChatMentionStore = create(\n  combine(\n    {\n      mentions: [] as BaseUser[],\n      queriedUsernames: [] as BaseUser[],\n      activeUsername: \"\",\n      iAmMentioned: 0,\n    },\n    (set) => ({\n      setMentions: (mentions: BaseUser[]) =>\n        set({\n          mentions,\n        }),\n      setQueriedUsernames: (queriedUsernames: BaseUser[]) =>\n        set({\n          queriedUsernames,\n        }),\n      setActiveUsername: (activeUsername: string) => {\n        return set({\n          activeUsername,\n        });\n      },\n      resetIAmMentioned: () =>\n        set({\n          iAmMentioned: 0,\n        }),\n      incrementIAmMentioned: () => {\n        useSoundEffectStore.getState().playSoundEffect(\"room_chat_mention\");\n        set((x) => ({ iAmMentioned: x.iAmMentioned + 1 }));\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/room/chat/useRoomChatStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\nimport { useRoomChatMentionStore } from \"./useRoomChatMentionStore\";\n\ninterface TextToken {\n  t: \"text\";\n  v: string;\n}\ninterface MentionToken {\n  t: \"mention\";\n  v: string;\n}\ninterface LinkToken {\n  t: \"link\";\n  v: string;\n}\n\ninterface BlockToken {\n  t: \"block\";\n  v: string;\n}\n\ninterface EmoteToken {\n  t: \"emote\";\n  v: string;\n}\n\nexport type RoomChatMessageToken =\n  | TextToken\n  | MentionToken\n  | LinkToken\n  | BlockToken\n  | EmoteToken;\n\nconst colors = [\n  \"#ff2366\",\n  \"#fd51d9\",\n  \"#face15\",\n  \"#8d4de8\",\n  \"#6859ea\",\n  \"#7ed321\",\n  \"#56b2ba\",\n  \"#00CCFF\",\n  \"#FF9900\",\n  \"#FFFF66\",\n];\n\nfunction generateColorFromString(str: string) {\n  let sum = 0;\n  for (let x = 0; x < str.length; x++) sum += x * str.charCodeAt(x);\n  return colors[sum % colors.length];\n}\n\nexport interface RoomChatMessage {\n  id: string;\n  userId: string;\n  avatarUrl: string;\n  color: string;\n  username: string;\n  displayName: string;\n  tokens: RoomChatMessageToken[];\n  deleted?: boolean;\n  deleterId?: string;\n  sentAt: string;\n  isWhisper?: boolean;\n}\n\nexport const useRoomChatStore = create(\n  combine(\n    {\n      open: true,\n      bannedUserIdMap: {} as Record<string, boolean>,\n      messages: [] as RoomChatMessage[],\n      newUnreadMessages: false,\n      message: \"\" as string,\n      isRoomChatScrolledToTop: false,\n    },\n    (set) => ({\n      addBannedUser: (userId: string) =>\n        set((s) => ({\n          messages: s.messages.filter((m) => m.userId !== userId),\n          bannedUserIdMap: { ...s.bannedUserIdMap, [userId]: true },\n        })),\n      addMessage: (m: RoomChatMessage) =>\n        set((s) => ({\n          newUnreadMessages: !s.open,\n          messages: [\n            { ...m, color: generateColorFromString(m.userId) },\n            ...(s.messages.length > 100\n              ? s.messages.slice(0, 100)\n              : s.messages),\n          ],\n        })),\n      setMessages: (messages: RoomChatMessage[]) =>\n        set((s) => ({\n          messages,\n        })),\n      clearChat: () =>\n        set({\n          messages: [],\n          newUnreadMessages: false,\n          bannedUserIdMap: {},\n        }),\n      reset: () =>\n        set({\n          messages: [],\n          newUnreadMessages: false,\n          open: false,\n          bannedUserIdMap: {},\n        }),\n      toggleOpen: () =>\n        set((s) => {\n          // Reset mention state\n          useRoomChatMentionStore.getState().resetIAmMentioned();\n          if (s.open) {\n            return {\n              open: false,\n              newUnreadMessages: false,\n            };\n          } else {\n            return {\n              open: true,\n              newUnreadMessages: false,\n            };\n          }\n        }),\n      setMessage: (message: string) =>\n        set({\n          message,\n        }),\n      setOpen: (open: boolean) => set((s) => ({ ...s, open })),\n      setIsRoomChatScrolledToTop: (isRoomChatScrolledToTop: boolean) =>\n        set({\n          isRoomChatScrolledToTop,\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/room/useOnRoomPage.tsx",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useOnRoomPage = create(\n  combine(\n    {\n      onRoomPage: false,\n    },\n    (set) => ({\n      setOnRoomPage: (onRoomPage: boolean) => {\n        set({ onRoomPage });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/room/useSplitUsersIntoSections.tsx",
    "content": "import { JoinRoomAndGetInfoResponse } from \"@dogehouse/kebab\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport React, { useContext } from \"react\";\nimport { RoomAvatar } from \"../../components/avatars/RoomAvatar\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { useConn } from \"../../shared-hooks/useConn\";\nimport { UserPreviewModalContext } from \"./UserPreviewModalProvider\";\n\nexport const useSplitUsersIntoSections = ({\n  room,\n  users,\n  activeSpeakerMap,\n  muteMap,\n}: JoinRoomAndGetInfoResponse) => {\n  const conn = useConn();\n  const { muted } = useMuteStore();\n  const { setData } = useContext(UserPreviewModalContext);\n  const navigation = useNavigation();\n  const speakers: React.ReactNode[] = [];\n  const askingToSpeak: React.ReactNode[] = [];\n  const listeners: React.ReactNode[] = [];\n  let canIAskToSpeak = false;\n\n  users.forEach((u) => {\n    let arr = listeners;\n    if (u.id === room.creatorId || u.roomPermissions?.isSpeaker) {\n      arr = speakers;\n    } else if (u.roomPermissions?.askedToSpeak) {\n      arr = askingToSpeak;\n    } else {\n      canIAskToSpeak = true;\n    }\n\n    const isCreator = u.id === room.creatorId;\n    const isSpeaker = !!u.roomPermissions?.isSpeaker;\n    const canSpeak = isCreator || isSpeaker;\n    const isMuted = conn.user.id === u.id ? muted : muteMap[u.id];\n\n    arr.push(\n      <RoomAvatar\n        key={u.id}\n        src={{ uri: u.avatarUrl }}\n        muted={canSpeak && isMuted}\n        username={u.displayName}\n        style={{ marginRight: 5, marginBottom: 10, flexBasis: \"23%\" }}\n        activeSpeaker={canSpeak && !isMuted && u.id in activeSpeakerMap}\n        onPress={() => {\n          setData({ userId: u.id });\n          navigation.navigate(\"RoomUserPreview\", { userId: u.id });\n        }}\n      />\n    );\n  });\n\n  return { speakers, listeners, askingToSpeak, canIAskToSpeak };\n};\n"
  },
  {
    "path": "pilaf/src/modules/schedule/ScheduleController.tsx",
    "content": "import React from \"react\";\nimport { ScrollView, StyleSheet, Text } from \"react-native\";\nimport { UpcomingRoomCard } from \"../../components/UpcomingRoomCard\";\nimport { colors, h3 } from \"../../constants/dogeStyle\";\nimport { useTypeSafeQuery } from \"../../shared-hooks/useTypeSafeQuery\";\n\nexport const ScheduleController: React.FC = () => {\n  const { data } = useTypeSafeQuery([\"getScheduledRooms\", \"\", false]);\n  return (\n    <ScrollView style={styles.safeAreaView}>\n      <Text style={styles.title}>Schedule</Text>\n      {data?.scheduledRooms.map((sr) => (\n        <UpcomingRoomCard\n          key={sr.id}\n          room={{\n            ...sr,\n            id: sr.id,\n            scheduledFor: new Date(sr.scheduledFor),\n            title: sr.name,\n            speakersInfo: {\n              avatars: [{ uri: sr.creator.avatarUrl }],\n              speakers: [sr.creator.username],\n            },\n          }}\n          style={{ marginBottom: 20 }}\n        />\n      ))}\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  safeAreaView: {\n    flex: 1,\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 20,\n    paddingTop: 10,\n  },\n  title: {\n    ...h3,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/search/SearchController.tsx",
    "content": "import React, { useState } from \"react\";\nimport { ScrollView, View } from \"react-native\";\nimport { SearchHeader } from \"../../components/header/SearchHeader\";\nimport { RoomSearchResult } from \"../../components/search/RoomSearchResult\";\nimport { SearchHistoryResult } from \"../../components/search/SearchHistoryResult\";\nimport { SearchHistoryResultList } from \"../../components/search/SearchHistoryResultList\";\nimport { UserSearchResult } from \"../../components/search/UserSearchResult\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nconst searchMocks = [\n  {\n    id: 0,\n    title:\n      \"Why CI & CD is important when working with a team and more because I need a long one\",\n    subtitle:\n      \"This is the subtitle This is the subtitle This is the subtitle This is the subtitle This is the subtitle\",\n    listeners: 300,\n  },\n  {\n    id: 1,\n    title: \"Why Elon Musk buys DogeCoin\",\n    subtitle: \"Because he can\",\n    listeners: 200,\n  },\n  {\n    id: 2,\n    title: \"The developer's hangout\",\n    subtitle: \"Terry Owen, Grace Abraham\",\n    listeners: 200,\n  },\n  {\n    id: 3,\n    title: \"Why we should remove React from Earth\",\n    subtitle: \"Because Angular is Better\",\n    listeners: 230000,\n  },\n];\n\nconst historyMocks = [\n  {\n    id: 0,\n    query: \"Elon\",\n  },\n  {\n    id: 1,\n    query: \"React\",\n  },\n];\n\nexport const SearchController: React.FC = () => {\n  const [query, setQuery] = useState(\"\");\n\n  return (\n    <>\n      <SearchHeader onTextChange={setQuery} text={query} />\n      <View\n        style={{\n          flex: 1,\n          backgroundColor: colors.primary900,\n          paddingVertical: 10,\n          paddingHorizontal: 25,\n        }}\n      >\n        {query.length === 0 && (\n          <SearchHistoryResultList>\n            {historyMocks.map((m) => (\n              <SearchHistoryResult\n                key={m.id}\n                query={m.query}\n                onPress={() => setQuery(m.query)}\n                onDeletePress={() => console.log(\"delete clic\")}\n              />\n            ))}\n          </SearchHistoryResultList>\n        )}\n        {query.length > 0 && (\n          <ScrollView style={{ backgroundColor: colors.primary900 }}>\n            {searchMocks\n              .filter((m) => m.title.includes(query))\n              .map((m) => (\n                <RoomSearchResult\n                  key={m.id}\n                  title={m.title}\n                  subtitle={m.subtitle}\n                  listeners={m.listeners}\n                />\n              ))}\n            <UserSearchResult\n              isOnline={true}\n              userName={\"The Real Anthony\"}\n              userLink={\"@anthonytheone\"}\n              userAvatarSrc={require(\"../../assets/images/100.png\")}\n            />\n          </ScrollView>\n        )}\n      </View>\n    </>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/settings/SettingsController.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nexport const SettingsController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Settings\"} showBackButton={true} />\n      <View style={styles.container} />\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/sound-effect/useSoundEffectStore.ts",
    "content": "import Sound from \"react-native-sound\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nSound.setCategory(\"Playback\");\n\nconst buildSound = (name: string): Sound => {\n  return new Sound(name, Sound.MAIN_BUNDLE, (error) => {\n    console.log(error);\n  });\n};\n\nexport const soundEffects = {\n  room_chat_mention: buildSound(\"room_chat_mention.wav\"),\n  unmute: buildSound(\"unmute.wav\"),\n  mute: buildSound(\"mute.wav\"),\n  room_invite: buildSound(\"room_invite.wav\"),\n};\n\nexport type PossibleSoundEffect = keyof typeof soundEffects;\n\nfunction getInitialSettings() {\n  const soundEffectSettings: Record<PossibleSoundEffect, boolean> = {\n    room_chat_mention: true,\n    unmute: true,\n    mute: true,\n    room_invite: true,\n  };\n\n  return soundEffectSettings;\n}\n\nexport const useSoundEffectStore = create(\n  combine(\n    {\n      soundEffect: soundEffects,\n      settings: getInitialSettings(),\n    },\n    (_, get) => ({\n      playSoundEffect: (se: keyof typeof soundEffects, force = false) => {\n        const { soundEffect, settings } = get();\n        if (force || settings[se]) {\n          soundEffect[se]?.play();\n        }\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/wallet/WalletController.tsx",
    "content": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { TitledHeader } from \"../../components/header/TitledHeader\";\nimport { colors } from \"../../constants/dogeStyle\";\n\nexport const WalletController: React.FC = () => {\n  return (\n    <>\n      <TitledHeader title={\"Wallet\"} showBackButton={true} />\n      <View style={styles.container} />\n    </>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/WebRtcApp.tsx",
    "content": "import React, { useContext, useEffect, useRef } from \"react\";\nimport InCallManager from \"react-native-incall-manager\";\nimport { useCurrentRoomIdStore } from \"../../global-stores/useCurrentRoomIdStore\";\nimport { useMuteStore } from \"../../global-stores/useMuteStore\";\nimport { InCallManagerStart } from \"../../lib/inCallManagerCenter\";\nimport * as RootNavigation from \"../../navigation/RootNavigation\";\nimport { WebSocketContext } from \"../ws/WebSocketProvider\";\nimport { useMicIdStore } from \"./stores/useMicIdStore\";\nimport { useVoiceStore } from \"./stores/useVoiceStore\";\nimport { consumeAudio } from \"./utils/consumeAudio\";\nimport { createTransport } from \"./utils/createTransport\";\nimport { joinRoom } from \"./utils/joinRoom\";\nimport { receiveVoice } from \"./utils/receiveVoice\";\nimport { sendVoice } from \"./utils/sendVoice\";\n\ninterface App2Props {}\n\nfunction closeVoiceConnections(_roomId: string | null) {\n  const { roomId, mic, nullify } = useVoiceStore.getState();\n  if (_roomId === null || _roomId === roomId) {\n    if (mic) {\n      console.log(\"stopping mic\");\n      mic.stop();\n    }\n\n    console.log(\"nulling transports\");\n    nullify();\n  }\n}\n\nexport const WebRtcApp: React.FC<App2Props> = () => {\n  const { conn } = useContext(WebSocketContext);\n  const { mic } = useVoiceStore();\n  const { micId } = useMicIdStore();\n  const { muted } = useMuteStore();\n  const { setCurrentRoomId } = useCurrentRoomIdStore();\n  const initialLoad = useRef(true);\n\n  useEffect(() => {\n    if (micId && !initialLoad.current) {\n      sendVoice();\n    }\n    initialLoad.current = false;\n  }, [micId]);\n  const consumerQueue = useRef<{ roomId: string; d: any }[]>([]);\n\n  async function flushConsumerQueue(_roomId: string) {\n    try {\n      for (const {\n        roomId,\n        d: { peerId, consumerParameters },\n      } of consumerQueue.current) {\n        if (_roomId === roomId) {\n          await consumeAudio(consumerParameters, peerId);\n        }\n      }\n    } catch (err) {\n      console.log(err);\n    } finally {\n      consumerQueue.current = [];\n    }\n  }\n  useEffect(() => {\n    if (mic) {\n      mic.enabled = !muted;\n    }\n  }, [mic, muted]);\n  useEffect(() => {\n    if (!conn) {\n      return;\n    }\n\n    const unsubs = [\n      // @todo fix\n      conn.addListener<any>(\"you_left_room\", (d) => {\n        if (d.kicked) {\n          const { currentRoomId } = useCurrentRoomIdStore.getState();\n          if (currentRoomId !== d.roomId) {\n            return;\n          }\n\n          setCurrentRoomId(null);\n          closeVoiceConnections(d.roomId);\n          RootNavigation.navigate(\"Home\");\n        }\n        InCallManager.stop();\n      }),\n      conn.addListener<any>(\"new-peer-speaker\", async (d) => {\n        const { roomId, recvTransport } = useVoiceStore.getState();\n        if (recvTransport && roomId === d.roomId) {\n          await consumeAudio(d.consumerParameters, d.peerId);\n        } else {\n          consumerQueue.current = [...consumerQueue.current, { roomId, d }];\n        }\n        InCallManagerStart();\n      }),\n      conn.addListener<any>(\"you-are-now-a-speaker\", async (d) => {\n        if (d.roomId !== useVoiceStore.getState().roomId) {\n          return;\n        }\n        try {\n          await createTransport(conn, d.roomId, \"send\", d.sendTransportOptions);\n        } catch (err) {\n          console.log(err);\n          return;\n        }\n        InCallManagerStart();\n        try {\n          await sendVoice();\n        } catch (err) {\n          console.log(err);\n        }\n      }),\n      conn.addListener<any>(\"you-joined-as-peer\", async (d) => {\n        closeVoiceConnections(null);\n        useVoiceStore.getState().set({ roomId: d.roomId });\n\n        consumerQueue.current = [];\n        console.log(\"creating a device\");\n        try {\n          await joinRoom(d.routerRtpCapabilities);\n        } catch (err) {\n          console.log(\"error creating a device | \", err);\n          return;\n        }\n        try {\n          await createTransport(conn, d.roomId, \"recv\", d.recvTransportOptions);\n        } catch (err) {\n          console.log(\"error creating recv transport | \", err);\n          return;\n        }\n        InCallManagerStart();\n        receiveVoice(conn, () => flushConsumerQueue(d.roomId));\n      }),\n      conn.addListener<any>(\"you-joined-as-speaker\", async (d) => {\n        closeVoiceConnections(null);\n        useVoiceStore.getState().set({ roomId: d.roomId });\n        // setStatus(\"connected-speaker\");\n        consumerQueue.current = [];\n        console.log(\"creating a device\");\n        try {\n          await joinRoom(d.routerRtpCapabilities);\n        } catch (err) {\n          console.log(\"error creating a device | \", err);\n          return;\n        }\n        try {\n          await createTransport(conn, d.roomId, \"send\", d.sendTransportOptions);\n        } catch (err) {\n          console.log(\"error creating send transport | \", err);\n          return;\n        }\n        console.log(\"sending voice\");\n        try {\n          await sendVoice();\n        } catch (err) {\n          console.log(\"error sending voice | \", err);\n          return;\n        }\n        await createTransport(conn, d.roomId, \"recv\", d.recvTransportOptions);\n        InCallManagerStart();\n        receiveVoice(conn, () => flushConsumerQueue(d.roomId));\n      }),\n    ];\n\n    return () => {\n      unsubs.forEach((x) => x());\n    };\n\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [conn]);\n\n  return <></>;\n};\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/components/MicPicker.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { useMicIdStore } from \"../stores/useMicIdStore\";\n\ninterface MicPickerProps {}\n\nexport const MicPicker: React.FC<MicPickerProps> = () => {\n  const { micId, setMicId } = useMicIdStore();\n  const [options, setOptions] = useState<\n    Array<{ id: string; label: string } | null>\n  >([]);\n  useEffect(() => {\n    navigator.mediaDevices\n      .enumerateDevices()\n      .then((x) =>\n        setOptions(\n          x.map((y) =>\n            y.kind !== \"audioinput\" ? null : { id: y.deviceId, label: y.label }\n          )\n        )\n      );\n  }, []);\n  return (\n    <>\n      {options.length === 0 ? <div>no mics available</div> : null}\n      {options.length ? (\n        <select\n          value={micId}\n          onChange={(e) => {\n            const id = e.target.value;\n            setMicId(id);\n          }}\n        >\n          {options.map((x) =>\n            !x ? null : (\n              <option key={x.id} value={x.id}>\n                {x.label}\n              </option>\n            )\n          )}\n        </select>\n      ) : null}\n    </>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useAskForMicStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAskForMicStore = create(\n  combine(\n    {\n      hasAsked: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useAudioTracks.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useAudioTracks = create(\n  combine(\n    {\n      tracks: [] as MediaStreamTrack[],\n    },\n    (set) => ({\n      add: (track: MediaStreamTrack) =>\n        set((s) => ({ tracks: [...s.tracks, track] })),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useConsumerStore.ts",
    "content": "import { Consumer } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useConsumerStore = create(\n  combine(\n    {\n      consumerMap: {} as Record<\n        string,\n        { consumer: Consumer; volume: number; debug?: boolean }\n      >,\n    },\n    (set) => ({\n      startDebugging: (userId: string) => {\n        set((s) => {\n          if (userId in s.consumerMap) {\n            return {\n              consumerMap: {\n                ...s.consumerMap,\n                [userId]: {\n                  ...s.consumerMap[userId],\n                  debug: true,\n                },\n              },\n            };\n          }\n\n          console.log(\"could not find consumer for \", userId);\n          return s;\n        });\n      },\n      setVolume: (userId: string, volume: number) => {\n        set((s) =>\n          userId in s.consumerMap\n            ? {\n                consumerMap: {\n                  ...s.consumerMap,\n                  [userId]: {\n                    ...s.consumerMap[userId],\n                    volume,\n                  },\n                },\n              }\n            : s\n        );\n      },\n      add: (c: Consumer, userId: string) =>\n        set((s) => {\n          let volume = 100;\n          if (userId in s.consumerMap) {\n            const x = s.consumerMap[userId];\n            volume = x.volume;\n            x.consumer.close();\n          }\n          return {\n            consumerMap: {\n              ...s.consumerMap,\n              [userId]: { consumer: c, volume },\n            },\n          };\n        }),\n      closeAll: () =>\n        set((s) => {\n          Object.values(s.consumerMap).forEach(\n            ({ consumer: c }) => !c.closed && c.close()\n          );\n          return {\n            consumerMap: {},\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useMicIdStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const MIC_KEY = \"micId\";\n\nconst getInitialState = () => {\n  try {\n    return localStorage.getItem(MIC_KEY) || \"\";\n  } catch {\n    return \"\";\n  }\n};\n\nexport const useMicIdStore = create(\n  combine(\n    {\n      micId: getInitialState(),\n    },\n    (set) => ({\n      setMicId: (id: string) => {\n        try {\n          localStorage.setItem(MIC_KEY, id);\n        } catch {}\n        set({ micId: id });\n      },\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useMicPermErrorStore.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useMicPermErrorStore = create(\n  combine(\n    {\n      error: false,\n    },\n    (set) => ({\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useProducerStore.ts",
    "content": "import { Producer } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const useProducerStore = create(\n  combine(\n    {\n      producer: null as Producer | null,\n    },\n    (set) => ({\n      add: (p: Producer) =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n          return { producer: p };\n        }),\n      close: () =>\n        set((s) => {\n          if (s.producer && !s.producer.closed) {\n            s.producer.close();\n          }\n          return {\n            producer: null,\n          };\n        }),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useSocketStatus.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype State =\n  | \"auth-good\"\n  | \"open\"\n  | \"connecting\"\n  | \"closed-by-server\"\n  | \"closed\";\n\nexport const useSocketStatus = create(\n  combine(\n    {\n      status: \"connecting\" as State,\n    },\n    (set) => ({\n      setStatus: (status: State) => set({ status }),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useStatus.ts",
    "content": "import create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\ntype State =\n  | \"init\"\n  | \"ws-disconnected\"\n  | \"voice-server-disconnected\"\n  | \"connected-no-room\"\n  | \"connected-listener\"\n  | \"bad-auth\"\n  | \"killed\"\n  | \"connected-speaker\";\n\nexport const useStatus = create(\n  combine(\n    {\n      status: \"init\" as State,\n    },\n    (set) => ({\n      setStatus: (status: State) => set({ status }),\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/stores/useVoiceStore.ts",
    "content": "import { Device } from \"mediasoup-client\";\nimport { detectDevice, Transport } from \"mediasoup-client/lib/types\";\nimport create from \"zustand\";\nimport { combine } from \"zustand/middleware\";\n\nexport const getDevice = async () => {\n  try {\n    let handlerName = detectDevice();\n    if (!handlerName) {\n      console.warn(\n        \"mediasoup does not recognize this device, so ben has defaulted it to Chrome74\"\n      );\n      handlerName = \"Chrome74\";\n    }\n    return new Device({ handlerName });\n  } catch {\n    return null;\n  }\n};\n\nexport const useVoiceStore = create(\n  combine(\n    {\n      roomId: \"\",\n      micStream: null as MediaStream | null,\n      mic: null as MediaStreamTrack | null,\n      recvTransport: null as Transport | null,\n      sendTransport: null as Transport | null,\n      device: undefined,\n    },\n    (set) => ({\n      prepare: async () => {\n        let d = await getDevice();\n        set({\n          device: d,\n        });\n      },\n      nullify: () =>\n        set({\n          recvTransport: null,\n          sendTransport: null,\n          roomId: \"\",\n          mic: null,\n          micStream: null,\n        }),\n      set,\n    })\n  )\n);\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/types.ts",
    "content": "export type AddWsListenerOnce = (op: string, fn: (d: any) => void) => void;\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/utils/consumeAudio.ts",
    "content": "import { useConsumerStore } from \"../stores/useConsumerStore\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport const consumeAudio = async (consumerParameters: any, peerId: string) => {\n  const { recvTransport } = useVoiceStore.getState();\n  if (!recvTransport) {\n    console.log(\"skipping consumeAudio because recvTransport is null\");\n    return false;\n  }\n  const consumer = await recvTransport.consume({\n    ...consumerParameters,\n    appData: {\n      peerId,\n      producerId: consumerParameters.producerId,\n      mediaTag: \"cam-audio\",\n    },\n  });\n  useConsumerStore.getState().add(consumer, peerId);\n\n  return true;\n};\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/utils/createTransport.ts",
    "content": "import { Connection } from \"@dogehouse/kebab/lib/raw\";\nimport { TransportOptions } from \"mediasoup-client/lib/types\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport async function createTransport(\n  conn: Connection,\n  _roomId: string,\n  direction: \"recv\" | \"send\",\n  transportOptions: TransportOptions\n) {\n  console.log(`create ${direction} transport`);\n  const { device, set } = useVoiceStore.getState();\n\n  // ask the server to create a server-side transport object and send\n  // us back the info we need to create a client-side transport\n  console.log(\"transport options\", transportOptions);\n  const transport =\n    direction === \"recv\"\n      ? await device!.createRecvTransport(transportOptions)\n      : await device!.createSendTransport(transportOptions);\n\n  // mediasoup-client will emit a connect event when media needs to\n  // start flowing for the first time. send dtlsParameters to the\n  // server, then call callback() on success or errback() on failure.\n  transport.on(\"connect\", ({ dtlsParameters }, callback, errback) => {\n    conn.once<any>(`@connect-transport-${direction}-done`, (d) => {\n      if (d.error) {\n        console.log(`connect-transport ${direction} failed`, d.error);\n        if (d.error.includes(\"already called\")) {\n          callback();\n        } else {\n          errback();\n        }\n      } else {\n        console.log(`connect-transport ${direction} success`);\n        callback();\n      }\n    });\n    conn.send(`@connect-transport`, {\n      transportId: transportOptions.id,\n      dtlsParameters,\n      direction,\n    });\n  });\n\n  if (direction === \"send\") {\n    // sending transports will emit a produce event when a new track\n    // needs to be set up to start sending. the producer's appData is\n    // passed as a parameter\n    transport.on(\n      \"produce\",\n      ({ kind, rtpParameters, appData }, callback, errback) => {\n        console.log(\"transport produce event\", appData.mediaTag);\n        // we may want to start out paused (if the checkboxes in the ui\n        // aren't checked, for each media type. not very clean code, here\n        // but, you know, this isn't a real application.)\n        // let paused = false;\n        // if (appData.mediaTag === \"cam-video\") {\n        //   paused = getCamPausedState();\n        // } else if (appData.mediaTag === \"cam-audio\") {\n        //   paused = getMicPausedState();\n        // }\n        // tell the server what it needs to know from us in order to set\n        // up a server-side producer object, and get back a\n        // producer.id. call callback() on success or errback() on\n        // failure.\n        conn.once<any>(`@send-track-${direction}-done`, (d) => {\n          if (d.error) {\n            console.log(`send-track ${direction} failed`, d.error);\n            errback();\n          } else {\n            console.log(`send-track-transport ${direction} success`);\n            callback({ id: d.id });\n          }\n        });\n\n        conn.send(\"@send-track\", {\n          transportId: transportOptions.id,\n          kind,\n          rtpParameters,\n          rtpCapabilities: device!.rtpCapabilities,\n          paused: false,\n          appData,\n          direction,\n        });\n      }\n    );\n  }\n\n  // for this simple demo, any time a transport transitions to closed,\n  // failed, or disconnected, leave the room and reset\n  //\n  transport.on(\"connectionstatechange\", (state) => {\n    console.log(\n      `${direction} transport ${transport.id} connectionstatechange ${state}`\n    );\n  });\n\n  if (direction === \"recv\") {\n    set({ recvTransport: transport });\n  } else {\n    set({ sendTransport: transport });\n  }\n}\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/utils/joinRoom.ts",
    "content": "import { RtpCapabilities } from \"mediasoup-client/lib/types\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport const joinRoom = async (routerRtpCapabilities: RtpCapabilities) => {\n  const { device } = useVoiceStore.getState();\n  if (!device!.loaded) {\n    await device!.load({ routerRtpCapabilities });\n  }\n};\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/utils/mergeRoomPermission.ts",
    "content": "import { RoomPermissions } from \"@dogehouse/kebab\";\n\nlet ipcRenderer: any = undefined;\nexport const mergeRoomPermission = (\n  currentRoomPermission: RoomPermissions | null | undefined,\n  newRoomPermissions: Partial<RoomPermissions>\n) => {\n  return {\n    ...(currentRoomPermission || {\n      askedToSpeak: false,\n      isMod: false,\n      isSpeaker: false,\n    }),\n    ...newRoomPermissions,\n  };\n};\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/utils/receiveVoice.ts",
    "content": "import { Connection } from \"@dogehouse/kebab/lib/raw\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\nimport { consumeAudio } from \"./consumeAudio\";\n\nexport const receiveVoice = (conn: Connection, flushQueue: () => void) => {\n  conn.once<any>(\"@get-recv-tracks-done\", async ({ consumerParametersArr }) => {\n    try {\n      for (const { peerId, consumerParameters } of consumerParametersArr) {\n        if (!(await consumeAudio(consumerParameters, peerId))) {\n          break;\n        }\n      }\n    } catch (err) {\n      console.log(err);\n    } finally {\n      flushQueue();\n    }\n  });\n  conn.send(\"@get-recv-tracks\", {\n    rtpCapabilities: useVoiceStore.getState().device!.rtpCapabilities,\n  });\n};\n"
  },
  {
    "path": "pilaf/src/modules/webrtc/utils/sendVoice.ts",
    "content": "import { useMicIdStore } from \"../stores/useMicIdStore\";\nimport { useMicPermErrorStore } from \"../stores/useMicPermErrorStore\";\nimport { useProducerStore } from \"../stores/useProducerStore\";\nimport { useVoiceStore } from \"../stores/useVoiceStore\";\n\nexport const sendVoice = async () => {\n  const { micId } = useMicIdStore.getState();\n  const { sendTransport, set, mic } = useVoiceStore.getState();\n  if (!sendTransport) {\n    console.log(\"no sendTransport in sendVoice\");\n    return;\n  }\n  mic?.stop();\n  // eslint-disable-next-line init-declarations\n  let micStream: MediaStream;\n  try {\n    micStream = await navigator.mediaDevices.getUserMedia({\n      audio: micId ? { deviceId: micId } : true,\n    });\n    useMicPermErrorStore.getState().set({ error: false });\n  } catch (err) {\n    set({ mic: null, micStream: null });\n    console.log(err);\n    useMicPermErrorStore.getState().set({ error: true });\n    return;\n  }\n\n  const audioTracks = micStream.getAudioTracks();\n\n  if (audioTracks.length) {\n    console.log(\"creating producer...\");\n    const track = audioTracks[0];\n    useProducerStore.getState().add(\n      await sendTransport.produce({\n        track,\n        appData: { mediaTag: \"cam-audio\" },\n      })\n    );\n    set({ mic: track, micStream });\n    return;\n  }\n\n  set({ mic: null, micStream: null });\n};\n"
  },
  {
    "path": "pilaf/src/modules/ws/WebSocketProvider.tsx",
    "content": "import { raw } from \"@dogehouse/kebab\";\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { useTokenStore } from \"../auth/useTokenStore\";\n\ninterface WebSocketProviderProps {\n  shouldConnect: boolean;\n}\n\ntype V = raw.Connection | null;\n\nexport const WebSocketContext = React.createContext<{ conn: V }>({\n  conn: null,\n});\n\nexport const WebSocketProvider: React.FC<WebSocketProviderProps> = ({\n  shouldConnect,\n  children,\n}) => {\n  const hasTokens = useTokenStore((s) => s.accessToken && s.refreshToken);\n  const [conn, setConn] = useState<V>(null);\n\n  useEffect(() => {\n    if (!conn && shouldConnect && hasTokens) {\n      const { accessToken, refreshToken } = useTokenStore.getState();\n      raw\n        .connect(accessToken, refreshToken, {\n          url: \"wss://api.dogehouse.tv/socket\",\n          // url: apiBaseUrl.replace(\"http\", \"ws\") + \"/socket\",\n        })\n        .then((x) => {\n          setConn(x);\n        })\n        .catch((err) => console.log(err));\n    }\n  }, [conn, shouldConnect, hasTokens]);\n\n  useEffect(() => {\n    if (!conn) {\n      return;\n    }\n\n    return conn.addListener<{\n      refreshToken: string;\n      accessToken: string;\n    }>(\"new-tokens\", ({ refreshToken, accessToken }) => {\n      useTokenStore.getState().setTokens({\n        accessToken,\n        refreshToken,\n      });\n    });\n  }, [conn]);\n\n  return (\n    <WebSocketContext.Provider value={useMemo(() => ({ conn }), [conn])}>\n      {children}\n    </WebSocketContext.Provider>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/navigation/AuthenticationSwitch.tsx",
    "content": "import React from \"react\";\nimport { useTokenStore } from \"../modules/auth/useTokenStore\";\nimport { LandingPage } from \"./LandingPage\";\nimport { MainNavigator } from \"./MainNavigator\";\n\nexport const AuthenticationSwitch: React.FC = () => {\n  const hasToken = useTokenStore((s) => !!s.accessToken && !!s.refreshToken);\n\n  if (!hasToken) {\n    return <LandingPage />;\n  }\n\n  return <MainNavigator />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/LandingPage.tsx",
    "content": "import React from \"react\";\nimport { LandingController } from \"../modules/landing/LandingController\";\n\nexport const LandingPage: React.FC = () => {\n  return <LandingController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/MainNavigator.tsx",
    "content": "import { createStackNavigator } from \"@react-navigation/stack\";\nimport React from \"react\";\nimport { Platform } from \"react-native\";\nimport { WaitForWsAndAuth } from \"../modules/auth/WaitForWsAndAuth\";\nimport MinimizedRoomCardController from \"../modules/room/MinimizedRoomCardController\";\nimport { HelpPage } from \"./mainNavigator/HelpPage\";\nimport { LanguagesPage } from \"./mainNavigator/LanguagesPage\";\nimport { MainPage } from \"./mainNavigator/MainPage\";\nimport { MessagesPage } from \"./mainNavigator/MessagesPage\";\nimport { NotificationsPage } from \"./mainNavigator/NotificationsPage\";\nimport { ProfilePage } from \"./mainNavigator/ProfilePage\";\nimport { ReportBugPage } from \"./mainNavigator/ReportBugPage\";\nimport { RoomPage } from \"./mainNavigator/RoomPage\";\nimport { SearchPage } from \"./mainNavigator/SearchPage\";\nimport { SettingsPage } from \"./mainNavigator/SettingsPage\";\nimport { WalletPage } from \"./mainNavigator/WalletPage\";\n\nexport type RootStackParamList = {\n  Main: undefined;\n  Notifications: undefined;\n  Search: undefined;\n  Messages: undefined;\n  Profile: undefined;\n  Settings: undefined;\n  Wallet: undefined;\n  Language: undefined;\n  Help: undefined;\n  ReportBug: undefined;\n  Room: { roomId: string };\n};\n\nconst Stack = createStackNavigator<RootStackParamList>();\n\nexport const MainNavigator = () => {\n  return (\n    <WaitForWsAndAuth>\n      <MinimizedRoomCardController />\n\n      <Stack.Navigator\n        mode=\"card\"\n        screenOptions={{\n          headerShown: false,\n          animationEnabled: Platform.OS === \"ios\",\n          // headerStyle: {\n          //   backgroundColor: colors.primary900,\n          //   borderBottomColor: colors.primary900,\n          //   shadowColor: colors.primary900,\n          // },\n          // headerTitleStyle: {\n          //   color: colors.text,\n          // },\n          // headerTintColor: colors.text,\n          // headerBackTitleVisible: false,\n        }}\n      >\n        <Stack.Screen\n          name=\"Main\"\n          component={MainPage}\n          options={{ headerShown: false }}\n        />\n        <Stack.Screen name=\"Notifications\" component={NotificationsPage} />\n        <Stack.Screen name=\"Search\" component={SearchPage} />\n        <Stack.Screen name=\"Messages\" component={MessagesPage} />\n        <Stack.Screen name=\"Profile\" component={ProfilePage} />\n        <Stack.Screen name=\"Settings\" component={SettingsPage} />\n        <Stack.Screen name=\"Wallet\" component={WalletPage} />\n        <Stack.Screen name=\"Language\" component={LanguagesPage} />\n        <Stack.Screen name=\"Help\" component={HelpPage} />\n        <Stack.Screen name=\"ReportBug\" component={ReportBugPage} />\n        <Stack.Screen name=\"Room\" component={RoomPage} />\n      </Stack.Navigator>\n    </WaitForWsAndAuth>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/navigation/RootNavigation.ts",
    "content": "import {\n  NavigationContainerRef,\n  ParamListBase,\n  RouteProp,\n  StackActions,\n} from \"@react-navigation/native\";\nimport * as React from \"react\";\nimport { StackNavigationProp } from \"@react-navigation/stack\";\n\n// This allow to get route and navigation props\n\nexport interface StackNavigationProps<\n  ParamList extends ParamListBase,\n  RouteName extends keyof ParamList = string\n> {\n  navigation: StackNavigationProp<ParamList, RouteName>;\n  route: RouteProp<ParamList, RouteName>;\n}\n\n// This allow to call navigation from outside a Navigator. Usefull for floating RoomController\n\nexport const navigationRef = React.createRef<NavigationContainerRef>();\n\nexport function navigate(name, params = {}) {\n  navigationRef.current?.dispatch(StackActions.popToTop());\n  navigationRef.current?.navigate(name, params);\n}\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/BottomNavigator.tsx",
    "content": "import { createBottomTabNavigator } from \"@react-navigation/bottom-tabs\";\nimport React from \"react\";\nimport { Image } from \"react-native\";\nimport { CreateRoomButton } from \"../../components/bottomBar/CreateRoomButton\";\nimport { colors } from \"../../constants/dogeStyle\";\nimport { ExplorePage } from \"./bottomNavigator/ExplorePage\";\nimport { FeedPage } from \"./bottomNavigator/FeedPage\";\nimport { FollowingPage } from \"./bottomNavigator/FollowingPage\";\nimport { SchedulePage } from \"./bottomNavigator/SchedulePage\";\nconst Tab = createBottomTabNavigator();\n\nconst EmptyComponent: React.FC = () => {\n  return null;\n};\n\nexport const BottomNavigator: React.FC = () => {\n  return (\n    <>\n      <Tab.Navigator\n        screenOptions={({ route }) => ({\n          tabBarIcon: ({ focused, color, size }) => {\n            let icon = require(\"../../assets/images/bottomBar/plus.png\");\n            if (route.name === \"Home\") {\n              icon = require(\"../../assets/images/bottomBar/sm-solid-home.png\");\n            } else if (route.name === \"Schedule\") {\n              icon = require(\"../../assets/images/bottomBar/ios-calendar.png\");\n            } else if (route.name === \"Following\") {\n              icon = require(\"../../assets/images/bottomBar/sm-solid-friends.png\");\n            } else if (route.name === \"Explore\") {\n              icon = require(\"../../assets/images/bottomBar/ios-compass.png\");\n            }\n            const tintColor = focused ? colors.accent : colors.text;\n            return <Image source={icon} style={{ tintColor: tintColor }} />;\n          },\n        })}\n        tabBarOptions={{\n          activeTintColor: colors.accent,\n          inactiveTintColor: colors.text,\n          activeBackgroundColor: colors.primary900,\n          inactiveBackgroundColor: colors.primary900,\n          style: {\n            backgroundColor: colors.primary900,\n            borderTopColor: colors.primary900,\n          },\n          showLabel: false,\n        }}\n      >\n        <Tab.Screen name=\"Home\" component={FeedPage} />\n        <Tab.Screen name=\"Schedule\" component={SchedulePage} />\n        <Tab.Screen\n          name=\"Create\"\n          component={EmptyComponent}\n          options={{ tabBarButton: (props) => <CreateRoomButton {...props} /> }}\n        />\n        <Tab.Screen name=\"Explore\" component={ExplorePage} />\n        <Tab.Screen name=\"Following\" component={FollowingPage} />\n      </Tab.Navigator>\n    </>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/HelpPage.tsx",
    "content": "import React from \"react\";\nimport { HelpController } from \"../../modules/help/HelpController\";\n\nexport const HelpPage: React.FC = () => {\n  return <HelpController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/InviteRoomPage.tsx",
    "content": "import { RouteProp } from \"@react-navigation/native\";\nimport React from \"react\";\nimport { InviteRoomController } from \"../../modules/room/InviteRoomController\";\nimport { RoomStackParamList } from \"./RoomNavigator\";\n\ntype RoomPageRouteProp = RouteProp<RoomStackParamList, \"RoomInvitation\">;\n\ntype InviteRoomPageProps = {\n  route: RoomPageRouteProp;\n};\n\nexport const InviteRoomPage: React.FC<InviteRoomPageProps> = ({ route }) => {\n  return <InviteRoomController room={route.params.room} />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/LanguagesPage.tsx",
    "content": "import React from \"react\";\nimport { LanguagesController } from \"../../modules/languages/LanguagesController\";\n\nexport const LanguagesPage: React.FC = () => {\n  return <LanguagesController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/MainPage.tsx",
    "content": "import React from \"react\";\nimport { MainController } from \"../../modules/main/MainController\";\n\nexport const MainPage: React.FC = () => {\n  return <MainController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/MessagesPage.tsx",
    "content": "import React from \"react\";\nimport { MessagesController } from \"../../modules/messages/MessagesController\";\n\nexport const MessagesPage: React.FC = () => {\n  return <MessagesController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/NotificationsPage.tsx",
    "content": "import React from \"react\";\nimport { NotificationsController } from \"../../modules/notifications/NotificationsController\";\n\nexport const NotificationsPage: React.FC = () => {\n  return <NotificationsController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/ProfilePage.tsx",
    "content": "import React from \"react\";\nimport { ProfileController } from \"../../modules/profile/ProfileController\";\n\nexport const ProfilePage: React.FC = () => {\n  return <ProfileController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/ReportBugPage.tsx",
    "content": "import React from \"react\";\nimport { ReportBugController } from \"../../modules/reportBug/ReportBugController\";\n\nexport const ReportBugPage: React.FC = () => {\n  return <ReportBugController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/RoomDescriptionPage.tsx",
    "content": "import { RouteProp } from \"@react-navigation/native\";\nimport React from \"react\";\nimport { RoomDescriptionController } from \"../../modules/room/RoomDescriptionController\";\nimport { RoomStackParamList } from \"./RoomNavigator\";\n\ntype RoomDescriptionPageRouteProp = RouteProp<\n  RoomStackParamList,\n  \"RoomDescription\"\n>;\n\ntype RoomDescriptionPageProps = {\n  route: RoomDescriptionPageRouteProp;\n};\n\nexport const RoomDescriptionPage: React.FC<RoomDescriptionPageProps> = ({\n  route,\n}) => {\n  return <RoomDescriptionController data={route.params.data} />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/RoomNavigator.tsx",
    "content": "import { JoinRoomAndGetInfoResponse, Room } from \"@dogehouse/kebab\";\nimport { createStackNavigator } from \"@react-navigation/stack\";\nimport React from \"react\";\nimport { Platform } from \"react-native\";\nimport { UserPreview } from \"../../components/UserPreview\";\nimport { WaitForWsAndAuth } from \"../../modules/auth/WaitForWsAndAuth\";\nimport { RoomChatMessage } from \"../../modules/room/chat/useRoomChatStore\";\nimport { RoomPanelController } from \"../../modules/room/RoomPanelController\";\nimport { InviteRoomPage } from \"./InviteRoomPage\";\nimport { RoomDescriptionPage } from \"./RoomDescriptionPage\";\n\nexport type RoomStackParamList = {\n  RoomMain: { data: JoinRoomAndGetInfoResponse };\n  RoomDescription: { data: JoinRoomAndGetInfoResponse };\n  RoomUserPreview: {\n    data: JoinRoomAndGetInfoResponse;\n    userId: string;\n    message?: RoomChatMessage;\n  };\n  RoomInvitation: { room: Room };\n};\n\nconst Stack = createStackNavigator<RoomStackParamList>();\n\ntype RoomNavigatorProps = {\n  data: JoinRoomAndGetInfoResponse;\n};\n\nexport const RoomNavigator: React.FC<RoomNavigatorProps> = ({ data }) => {\n  return (\n    <WaitForWsAndAuth>\n      <Stack.Navigator\n        screenOptions={{\n          headerShown: false,\n          animationEnabled: Platform.OS === \"ios\",\n        }}\n      >\n        <Stack.Screen\n          name=\"RoomMain\"\n          component={RoomPanelController}\n          initialParams={{ data }}\n        />\n        <Stack.Screen name=\"RoomDescription\" component={RoomDescriptionPage} />\n        <Stack.Screen name=\"RoomInvitation\" component={InviteRoomPage} />\n        <Stack.Screen\n          name=\"RoomUserPreview\"\n          component={UserPreview}\n          initialParams={{ data }}\n        />\n      </Stack.Navigator>\n    </WaitForWsAndAuth>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/RoomPage.tsx",
    "content": "import React from \"react\";\nimport { RoomController } from \"../../modules/room/RoomController\";\nimport { RootStackParamList } from \"../MainNavigator\";\nimport { StackNavigationProps } from \"../RootNavigation\";\n\nexport const RoomPage: React.FC<\n  StackNavigationProps<RootStackParamList, \"Room\">\n> = ({ route }) => {\n  return <RoomController roomId={route.params.roomId} />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/SearchPage.tsx",
    "content": "import React from \"react\";\nimport { SearchController } from \"../../modules/search/SearchController\";\n\nexport const SearchPage: React.FC = () => {\n  return <SearchController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/SettingsPage.tsx",
    "content": "import React from \"react\";\nimport { SettingsController } from \"../../modules/settings/SettingsController\";\n\nexport const SettingsPage: React.FC = () => {\n  return <SettingsController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/WalletPage.tsx",
    "content": "import React from \"react\";\nimport { WalletController } from \"../../modules/wallet/WalletController\";\n\nexport const WalletPage: React.FC = () => {\n  return <WalletController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/bottomNavigator/ExplorePage.tsx",
    "content": "import React from \"react\";\nimport { ExploreController } from \"../../../modules/explore/ExploreController\";\n\nexport const ExplorePage: React.FC = () => {\n  return <ExploreController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/bottomNavigator/FeedPage.tsx",
    "content": "import React from \"react\";\nimport { ScrollView, StyleSheet } from \"react-native\";\nimport { colors, h3 } from \"../../../constants/dogeStyle\";\nimport { FeedController } from \"../../../modules/feed/FeedController\";\n\nexport const FeedPage: React.FC = () => {\n  return (\n    <ScrollView style={styles.container}>\n      <FeedController />\n    </ScrollView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: colors.primary900,\n    paddingHorizontal: 20,\n    paddingTop: 10,\n  },\n  title: {\n    ...h3,\n    marginBottom: 20,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/bottomNavigator/FollowingPage.tsx",
    "content": "import React from \"react\";\nimport { FollowingOnlineController } from \"../../../modules/following/FollowingOnlineController\";\n\nexport const FollowingPage: React.FC = () => {\n  return <FollowingOnlineController />;\n};\n"
  },
  {
    "path": "pilaf/src/navigation/mainNavigator/bottomNavigator/SchedulePage.tsx",
    "content": "import React from \"react\";\nimport { ScheduleController } from \"../../../modules/schedule/ScheduleController\";\n\nexport const SchedulePage: React.FC = () => {\n  return <ScheduleController />;\n};\n"
  },
  {
    "path": "pilaf/src/pages/CreateRoomPage.tsx",
    "content": "import SegmentedControl from \"@react-native-segmented-control/segmented-control\";\nimport { useNavigation } from \"@react-navigation/core\";\nimport { Formik } from \"formik\";\nimport React, { useState } from \"react\";\nimport {\n  KeyboardAvoidingView,\n  ScrollView,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { Spinner } from \"../components/Spinner\";\nimport {\n  colors,\n  fontFamily,\n  fontSize,\n  h4,\n  paragraph,\n  paragraphBold,\n  radius,\n  small,\n} from \"../constants/dogeStyle\";\nimport { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\nimport { useRoomChatStore } from \"../modules/room/chat/useRoomChatStore\";\nimport { useWrappedConn } from \"../shared-hooks/useConn\";\nimport { useTypeSafePrefetch } from \"../shared-hooks/useTypeSafePrefetch\";\n\ninterface CreateRoomModalProps {\n  onRequestClose: () => void;\n  name?: string;\n  description?: string;\n  isPrivate?: boolean;\n  edit?: boolean;\n}\n\nexport const CreateRoomPage: React.FC<CreateRoomModalProps> = ({\n  onRequestClose,\n  name: currentName,\n  description: currentDescription,\n  isPrivate,\n  edit,\n}) => {\n  const [segmentIndex, setSegmentIndex] = useState(0);\n  const conn = useWrappedConn();\n  const prefetch = useTypeSafePrefetch();\n  const navigation = useNavigation();\n  const inset = useSafeAreaInsets();\n  const [clearChat] = useRoomChatStore((s) => [s.clearChat]);\n  const [loading, setLoading] = useState(false);\n  return (\n    <KeyboardAvoidingView style={{ flex: 1 }} behavior={\"padding\"}>\n      <View\n        style={[\n          styles.container,\n          { paddingBottom: 20 + inset.bottom, paddingTop: 20 + inset.top },\n        ]}\n      >\n        <Formik<{\n          name: string;\n          privacy: string;\n          description: string;\n        }>\n          initialValues={{\n            name: currentName || \"\",\n            description: currentDescription || \"\",\n            privacy: isPrivate ? \"private\" : \"public\",\n          }}\n          validateOnChange={false}\n          validateOnBlur={false}\n          validate={({ name, description }) => {\n            const errors: Record<string, string> = {};\n\n            if (name.length < 2 || name.length > 60) {\n              return {\n                name: \"name error\",\n              };\n            } else if (description.length > 500) {\n              return {\n                description: \"Description error\",\n              };\n            }\n\n            return errors;\n          }}\n          onSubmit={async ({ name, privacy, description }) => {\n            setLoading(true);\n            const d = { name, privacy, description };\n            const resp = edit\n              ? await conn.mutation.editRoom(d)\n              : await conn.mutation.createRoom(d);\n\n            if (\"error\" in resp) {\n              //showErrorToast(resp.error);\n              setLoading(false);\n              return;\n            } else if (resp.room) {\n              const { room } = resp;\n              clearChat();\n              prefetch([\"joinRoomAndGetInfo\", room.id], [room.id]);\n              useCurrentRoomIdStore.getState().setCurrentRoomId(room.id);\n              navigation.navigate(\"Room\", { roomId: room.id });\n              onRequestClose();\n              setLoading(false);\n            }\n          }}\n        >\n          {({ values, setFieldValue, handleChange, handleSubmit, errors }) => (\n            <ScrollView keyboardShouldPersistTaps=\"handled\">\n              <Text style={styles.titleText}>Create Room</Text>\n              <Text style={styles.descriptionText}>\n                Fill the following fields to start a new room\n              </Text>\n              <TextInput\n                placeholder={\"Room name\"}\n                placeholderTextColor={colors.primary300}\n                style={[\n                  styles.roomNameEditText,\n                  errors.name && {\n                    borderWidth: 1,\n                    borderColor: colors.secondary,\n                  },\n                ]}\n                autoFocus={true}\n                value={values.name}\n                onChangeText={handleChange(\"name\")}\n              />\n              {errors.name && (\n                <Text style={styles.errorMessage}>{errors.name}</Text>\n              )}\n              <SegmentedControl\n                style={styles.segment}\n                tintColor={colors.accent}\n                backgroundColor={colors.primary700}\n                values={[\"Public\", \"Private\"]}\n                fontStyle={{\n                  fontFamily: fontFamily.bold,\n                  fontSize: fontSize.paragraph,\n                  fontWeight: \"700\",\n                  color: colors.text,\n                }}\n                activeFontStyle={{\n                  fontFamily: fontFamily.bold,\n                  fontSize: fontSize.paragraph,\n                  fontWeight: \"700\",\n                  color: colors.text,\n                }}\n                selectedIndex={segmentIndex}\n                onChange={(event) => {\n                  setSegmentIndex(event.nativeEvent.selectedSegmentIndex);\n                  setFieldValue(\"privacy\", event.nativeEvent.value);\n                }}\n              />\n              <TextInput\n                placeholder={\"Room description\"}\n                placeholderTextColor={colors.primary300}\n                multiline={true}\n                style={[\n                  styles.roomDescriptionEditText,\n                  errors.description && {\n                    borderWidth: 1,\n                    borderColor: colors.secondary,\n                  },\n                ]}\n                value={values.description}\n                onChangeText={handleChange(\"description\")}\n              />\n              {errors.description && (\n                <Text style={styles.errorMessage}>{errors.description}</Text>\n              )}\n              <View style={styles.buttonsContainer}>\n                <TouchableOpacity\n                  style={styles.createButton}\n                  onPress={() => handleSubmit()}\n                >\n                  {loading ? (\n                    <Spinner />\n                  ) : (\n                    <Text style={styles.createButtonText}>Create room</Text>\n                  )}\n                </TouchableOpacity>\n                <TouchableOpacity\n                  style={styles.cancelButton}\n                  onPress={() => onRequestClose()}\n                >\n                  <Text style={styles.cancelButtonText}>Cancel</Text>\n                </TouchableOpacity>\n              </View>\n            </ScrollView>\n          )}\n        </Formik>\n      </View>\n    </KeyboardAvoidingView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    backgroundColor: colors.primary800,\n  },\n  titleText: {\n    ...h4,\n  },\n  descriptionText: {\n    ...paragraph,\n    marginTop: 16,\n    color: colors.primary300,\n  },\n  roomNameEditText: {\n    height: 40,\n    backgroundColor: colors.primary700,\n    paddingHorizontal: 16,\n    borderRadius: radius.m,\n    marginTop: 16,\n    color: colors.text,\n  },\n  roomDescriptionEditText: {\n    height: 200,\n    backgroundColor: colors.primary700,\n    padding: 16,\n    borderRadius: radius.m,\n    marginTop: 16,\n    color: colors.text,\n  },\n  buttonsContainer: {\n    marginTop: 16,\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-evenly\",\n  },\n  createButton: {\n    flex: 1,\n    backgroundColor: colors.accent,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    borderRadius: radius.m,\n    height: 38,\n  },\n  createButtonText: {\n    ...paragraphBold,\n    alignSelf: \"center\",\n  },\n  cancelButton: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    borderRadius: radius.m,\n    height: 38,\n  },\n  cancelButtonText: {\n    ...paragraphBold,\n    alignSelf: \"center\",\n    textDecorationLine: \"underline\",\n  },\n  segment: {\n    marginTop: 16,\n  },\n  dropDownContainer: {\n    backgroundColor: colors.primary700,\n  },\n  dropDownItem: {\n    backgroundColor: colors.primary700,\n  },\n  dropDownPickerCustom: {\n    backgroundColor: colors.primary700,\n    borderWidth: 0,\n    color: colors.primary300,\n  },\n  errorMessage: {\n    ...small,\n    marginLeft: 16,\n    color: colors.secondary,\n  },\n});\n"
  },
  {
    "path": "pilaf/src/shared-components/ApiPreloadLink.tsx",
    "content": "import React from \"react\";\nimport { useTypeSafePrefetch } from \"../shared-hooks/useTypeSafePrefetch\";\n\ntype Prefetch = ReturnType<typeof useTypeSafePrefetch>;\n\nconst handlers = {\n  following: ({ username }: { username: string }) => ({\n    href: \"/u/[username]/following\",\n    as: `/u/${username}/following`,\n    onClick: (prefetch: Prefetch) =>\n      prefetch(\"getFollowList\", [username, true, 0]),\n  }),\n  followers: ({ username }: { username: string }) => ({\n    href: \"/u/[username]/followers\",\n    as: `/u/${username}/followers`,\n    onClick: (prefetch: Prefetch) =>\n      prefetch(\"getFollowList\", [username, false, 0]),\n  }),\n  profile: ({ username }: { username: string }) => ({\n    href: \"/u/[username]\",\n    as: `/u/${username}`,\n    onClick: (prefetch: Prefetch) => prefetch(\"getUserProfile\", [username]),\n  }),\n  room: ({ id }: { id: string }) => ({\n    href: \"/room/[id]\",\n    as: `/room/${id}`,\n    onClick: (prefetch: Prefetch) => prefetch(\"joinRoomAndGetInfo\", [id]),\n  }),\n};\n\ntype Handler = typeof handlers;\n\ntype ValueOf<T> = T[keyof T];\ntype DifferentProps = {\n  [K in keyof Handler]: {\n    route: K;\n    data: Parameters<Handler[K]>[0];\n  };\n};\n\n// the purpose of this component is to start the query to the api before navigating to the page\n// this will result in less loading time for the user\nexport const ApiPreloadLink: React.FC<ValueOf<DifferentProps>> = ({\n  children,\n  route,\n  data,\n}) => {\n  const prefetch = useTypeSafePrefetch();\n\n  const { as, href, onClick } = handlers[route](data);\n\n  return (\n    <></>\n    // <Link href={href} as={as}>\n    //   <a onClick={() => onClick(prefetch)}>{children}</a>\n    // </Link>\n  );\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useConn.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useContext } from \"react\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\n\nexport const useConn = () => {\n  return useContext(WebSocketContext).conn!;\n};\n\nexport const useWrappedConn = () => {\n  return wrap(useContext(WebSocketContext).conn!);\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useCurrentRoomInfo.ts",
    "content": "import { useContext } from \"react\";\nimport { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { useTypeSafeQuery } from \"./useTypeSafeQuery\";\n\nexport const useCurrentRoomInfo = () => {\n  const { currentRoomId } = useCurrentRoomIdStore();\n  const { data } = useTypeSafeQuery(\n    [\"joinRoomAndGetInfo\", currentRoomId || \"\"],\n    {\n      enabled: !!currentRoomId,\n    },\n    [currentRoomId || \"\"]\n  );\n  const { conn } = useContext(WebSocketContext);\n\n  if (!data || !conn || !currentRoomId || \"error\" in data) {\n    return {\n      isMod: false,\n      isCreator: false,\n      isSpeaker: false,\n      canSpeak: false,\n    };\n  }\n\n  let isMod = false;\n  let isSpeaker = false;\n\n  const { users } = data;\n  const me = conn.user;\n\n  for (const u of users) {\n    if (u.id === me.id) {\n      if (u.roomPermissions?.isSpeaker) {\n        isSpeaker = true;\n      }\n      if (u.roomPermissions?.isMod) {\n        isMod = true;\n      }\n      break;\n    }\n  }\n\n  const isCreator = me.id === data.room.creatorId;\n\n  return {\n    isCreator,\n    isMod,\n    isSpeaker,\n    canSpeak: isCreator || isSpeaker,\n  };\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useMainWsHandler.tsx",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport React, { FC, useContext, useEffect } from \"react\";\nimport { useCurrentRoomIdStore } from \"../global-stores/useCurrentRoomIdStore\";\nimport { useRoomChatMentionStore } from \"../global-stores/useRoomChatMentionStore\";\nimport { pushRoomCreateNotification } from \"../lib/notificationCenter\";\n// import { showErrorToast } from \"../lib/showErrorToast\";\nimport { useTokenStore } from \"../modules/auth/useTokenStore\";\nimport {\n  RoomChatMessageToken,\n  useRoomChatStore,\n} from \"../modules/room/chat/useRoomChatStore\";\nimport { mergeRoomPermission } from \"../modules/webrtc/utils/mergeRoomPermission\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { setMute } from \"./useSetMute\";\nimport { useTypeSafeUpdateQuery } from \"./useTypeSafeUpdateQuery\";\n\nexport const useMainWsHandler = () => {\n  const { conn } = useContext(WebSocketContext);\n  const updateQuery = useTypeSafeUpdateQuery();\n\n  useEffect(() => {\n    if (!conn) {\n      return;\n    }\n\n    const unsubs = [\n      conn.addListener<any>(\n        \"new_room_details\",\n        ({ name, description, isPrivate, roomId }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  room: {\n                    ...data.room,\n                    name,\n                    description,\n                    isPrivate,\n                  },\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"chat_user_banned\", ({ userId }) => {\n        useRoomChatStore.getState().addBannedUser(userId);\n      }),\n      conn.addListener<any>(\"new_chat_msg\", ({ msg }) => {\n        const { open } = useRoomChatStore.getState();\n        useRoomChatStore.getState().addMessage(msg);\n        const { isRoomChatScrolledToTop } = useRoomChatStore.getState();\n        if (\n          (!open || isRoomChatScrolledToTop) &&\n          !!msg.tokens.filter(\n            (t: RoomChatMessageToken) =>\n              t.t === \"mention\" &&\n              t.v?.toLowerCase() === conn.user.username.toLowerCase()\n          ).length\n        ) {\n          useRoomChatMentionStore.getState().incrementIAmMentioned();\n        }\n      }),\n      conn.addListener<any>(\"message_deleted\", ({ messageId, deleterId }) => {\n        const { messages, setMessages } = useRoomChatStore.getState();\n        setMessages(\n          messages.map((m) => ({\n            ...m,\n            deleted: m.id === messageId || !!m.deleted,\n            deleterId: m.id === messageId ? deleterId : m.deleterId,\n          }))\n        );\n      }),\n      conn.addListener<any>(\n        \"room_privacy_change\",\n        ({ roomId, isPrivate, name }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  room: {\n                    ...data.room,\n                    name,\n                    isPrivate,\n                  },\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"banned\", () => {\n        //showErrorToast(\"you got banned\");\n        conn.close();\n        useTokenStore\n          .getState()\n          .setTokens({ accessToken: \"\", refreshToken: \"\" });\n      }),\n      conn.addListener<any>(\"ban_done\", ({ worked }) => {\n        // if (worked) {\n        //   showErrorToast(\"ban worked\");\n        // } else {\n        //   showErrorToast(\"ban failed\");\n        // }\n      }),\n      conn.addListener<any>(\"someone_you_follow_created_a_room\", (value) => {\n        pushRoomCreateNotification(\n          value.username,\n          value.roomName,\n          value.roomId\n        );\n      }),\n      conn.addListener<any>(\"invitation_to_room\", (value) => {\n        pushRoomCreateNotification(\n          value.username,\n          value.roomName,\n          value.roomId\n        );\n      }),\n      conn.addListener<any>(\n        \"active_speaker_change\",\n        ({ roomId, activeSpeakerMap }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  activeSpeakerMap,\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"room_destroyed\", ({ roomId }) => {\n        useCurrentRoomIdStore\n          .getState()\n          .setCurrentRoomId((id) => (id === roomId ? null : id));\n\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => ({\n          // @todo change to an error code\n          error: \"room gone\",\n        }));\n      }),\n      conn.addListener<any>(\"new_room_creator\", ({ userId, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          \"error\" in data\n            ? data\n            : {\n                ...data,\n                room: {\n                  ...data.room,\n                  creatorId: userId,\n                },\n              }\n        );\n      }),\n      conn.addListener<any>(\n        \"speaker_removed\",\n        ({ userId, roomId, muteMap }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  muteMap,\n                  users: data.users.map((x) =>\n                    userId === x.id\n                      ? {\n                          ...x,\n                          roomPermissions: mergeRoomPermission(\n                            x.roomPermissions,\n                            { isSpeaker: false, askedToSpeak: false }\n                          ),\n                        }\n                      : x\n                  ),\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"speaker_added\", ({ userId, roomId, muteMap }) => {\n        // Mute user upon added as speaker\n        if (conn.user.id === userId) {\n          setMute(wrap(conn), true);\n        }\n\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          \"error\" in data\n            ? data\n            : {\n                ...data,\n                muteMap,\n                users: data.users.map((x) =>\n                  userId === x.id\n                    ? {\n                        ...x,\n                        roomPermissions: mergeRoomPermission(\n                          x.roomPermissions,\n                          {\n                            isSpeaker: true,\n                          }\n                        ),\n                      }\n                    : x\n                ),\n              }\n        );\n      }),\n      conn.addListener<any>(\"mod_changed\", ({ userId, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          \"error\" in data\n            ? data\n            : {\n                ...data,\n                users: data.users.map((x) =>\n                  userId === x.id\n                    ? {\n                        ...x,\n                        roomPermissions: mergeRoomPermission(\n                          x.roomPermissions,\n                          { isMod: !x.roomPermissions?.isMod }\n                        ),\n                      }\n                    : x\n                ),\n              }\n        );\n      }),\n      conn.addListener<any>(\"user_left_room\", ({ userId, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => {\n          if (\"error\" in data) {\n            return data;\n          }\n\n          const { [userId]: _, ...asm } = data.activeSpeakerMap;\n          return {\n            ...data,\n            activeSpeakerMap: asm,\n            room: {\n              ...data.room,\n              peoplePreviewList: data.room.peoplePreviewList.filter(\n                (x) => x.id !== userId\n              ),\n              numPeopleInside: data.room.numPeopleInside - 1,\n            },\n            users: data.users.filter((x) => x.id !== userId),\n          };\n        });\n      }),\n      conn.addListener<any>(\n        \"new_user_join_room\",\n        ({ user, muteMap, roomId }) => {\n          updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n            \"error\" in data\n              ? data\n              : {\n                  ...data,\n                  muteMap,\n                  room: {\n                    ...data.room,\n                    peoplePreviewList:\n                      data.room.peoplePreviewList.length < 10\n                        ? [\n                            ...data.room.peoplePreviewList,\n                            {\n                              id: user.id,\n                              displayName: user.displayName,\n                              numFollowers: user.numFollowers,\n                            },\n                          ]\n                        : data.room.peoplePreviewList,\n                    numPeopleInside: data.room.numPeopleInside + 1,\n                  },\n                  users: [...data.users.filter((x) => x.id !== user.id), user],\n                }\n          );\n        }\n      ),\n      conn.addListener<any>(\"hand_raised\", ({ roomId, userId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) =>\n          \"error\" in data\n            ? data\n            : {\n                ...data,\n                users: data.users.map((u) =>\n                  u.id === userId\n                    ? {\n                        ...u,\n                        roomPermissions: mergeRoomPermission(\n                          u.roomPermissions,\n                          {\n                            askedToSpeak: true,\n                          }\n                        ),\n                      }\n                    : u\n                ),\n              }\n        );\n      }),\n      conn.addListener<any>(\"mute_changed\", ({ userId, value, roomId }) => {\n        updateQuery([\"joinRoomAndGetInfo\", roomId], (data) => {\n          if (\"error\" in data) {\n            return data;\n          }\n          let muteMap = data.muteMap;\n          if (value) {\n            muteMap = { ...data.muteMap, [userId]: true };\n          } else {\n            const { [userId]: _, ...newMm } = data.muteMap;\n            muteMap = newMm;\n          }\n          return {\n            ...data,\n            muteMap,\n          };\n        });\n      }),\n    ];\n\n    return () => {\n      unsubs.forEach((u) => u());\n    };\n  }, [conn, updateQuery]);\n};\n\nexport const MainWsHandlerProvider: FC = ({ children }) => {\n  useMainWsHandler();\n  return <>{children}</>;\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useSetMute.ts",
    "content": "import { Wrapper } from \"@dogehouse/kebab\";\nimport { useMuteStore } from \"../global-stores/useMuteStore\";\nimport { useWrappedConn } from \"./useConn\";\n\nexport const useSetMute = () => {\n  const conn = useWrappedConn();\n  const { setInternalMute } = useMuteStore();\n  return (mute: boolean) => {\n    setInternalMute(mute);\n    conn.mutation.setMute(mute);\n  };\n};\n\nexport const setMute = (conn: Wrapper, value: boolean) => {\n  useMuteStore.getState().setInternalMute(value);\n  conn.mutation.setMute(value);\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useTypeSafeMutation.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useContext } from \"react\";\nimport { useMutation, UseMutationOptions } from \"react-query\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\nimport { Await } from \"../types/util-types\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"mutation\"];\n\nexport const useTypeSafeMutation = <K extends Keys>(\n  key: K,\n  opts?: UseMutationOptions<\n    Await<ReturnType<ReturnType<typeof wrap>[\"mutation\"][K]>>,\n    any,\n    Parameters<ReturnType<typeof wrap>[\"mutation\"][K]>,\n    any\n  >\n) => {\n  const { conn } = useContext(WebSocketContext);\n\n  return useMutation<\n    Await<ReturnType<ReturnType<typeof wrap>[\"mutation\"][K]>>,\n    any,\n    Parameters<ReturnType<typeof wrap>[\"mutation\"][K]>\n  >(\n    (params) =>\n      (wrap(conn!).mutation[typeof key === \"string\" ? key : key[0]] as any)(\n        ...params\n      ),\n    opts\n  );\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useTypeSafePrefetch.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useCallback, useContext } from \"react\";\nimport { useQueryClient } from \"react-query\";\nimport { WebSocketContext } from \"../modules/ws/WebSocketProvider\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"query\"];\n\ntype PaginatedKey<K extends Keys> = [K, string | number];\n\nexport const useTypeSafePrefetch = () => {\n  const { conn } = useContext(WebSocketContext);\n  const client = useQueryClient();\n\n  return useCallback(\n    <K extends Keys>(\n      key: K | PaginatedKey<K>,\n      params?: Parameters<ReturnType<typeof wrap>[\"query\"][K]>\n    ) =>\n      client.prefetchQuery(\n        key,\n        () =>\n          (wrap(conn!).query[typeof key === \"string\" ? key : key[0]] as any)(\n            ...(params || [])\n          ),\n        { staleTime: 0 }\n      ),\n    [conn, client]\n  );\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useTypeSafeQuery.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useQuery, UseQueryOptions } from \"react-query\";\nimport { Await } from \"../types/util-types\";\nimport { useWrappedConn } from \"./useConn\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"query\"];\n\ntype PaginatedKey<K extends Keys> = [K, ...(string | number | boolean)[]];\n\nexport const useTypeSafeQuery = <K extends Keys>(\n  key: K | PaginatedKey<K>,\n  opts?: UseQueryOptions,\n  params?: Parameters<ReturnType<typeof wrap>[\"query\"][K]>\n) => {\n  const conn = useWrappedConn();\n\n  return useQuery<Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>>(\n    key,\n    () => {\n      const fn = conn.query[typeof key === \"string\" ? key : key[0]] as any;\n      return fn(...(params || []));\n    },\n    {\n      enabled: !!conn,\n      ...opts,\n    } as any\n  );\n};\n"
  },
  {
    "path": "pilaf/src/shared-hooks/useTypeSafeUpdateQuery.ts",
    "content": "import { wrap } from \"@dogehouse/kebab\";\nimport { useCallback } from \"react\";\nimport { useQueryClient } from \"react-query\";\nimport { Await } from \"../types/util-types\";\n\ntype Keys = keyof ReturnType<typeof wrap>[\"query\"];\n\ntype PaginatedKey<K extends Keys> = [K, string | number];\n\nexport const useTypeSafeUpdateQuery = () => {\n  const client = useQueryClient();\n  return useCallback(\n    <K extends Keys>(\n      key: K | PaginatedKey<K>,\n      fn: (\n        x: Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>\n      ) => Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>\n    ) => {\n      client.setQueryData<\n        Await<ReturnType<ReturnType<typeof wrap>[\"query\"][K]>>\n      >(key, fn as any);\n    },\n    [client]\n  );\n};\n"
  },
  {
    "path": "pilaf/src/stories/index.ts",
    "content": "import \"./Button.stories\";\nimport \"./FollowNotification.stories\";\nimport \"./GenericNotification.stories\";\nimport \"./LiveNotification.stories\";\nimport \"./Message.stories\";\nimport \"./MultipleUserAvatar.stories\";\nimport \"./NewRoomNotification.stories\";\nimport \"./SingleUserAvatar.stories\";\n"
  },
  {
    "path": "pilaf/src/types/util-types.ts",
    "content": "export type Await<T> = T extends Promise<infer U> ? U : T;\n"
  },
  {
    "path": "pilaf/storybook/addons.ts",
    "content": "import \"@storybook/addon-actions/register\";\nimport \"@storybook/addon-links/register\";\nimport \"@storybook/addon-knobs/register\";\n"
  },
  {
    "path": "pilaf/storybook/index.ts",
    "content": "// if you use expo remove this line\nimport { AppRegistry, Platform } from \"react-native\";\n\nimport {\n  getStorybookUI,\n  configure,\n  addDecorator,\n} from \"@storybook/react-native\";\nimport { withKnobs } from \"@storybook/addon-knobs\";\n\nimport \"./rn-addons\";\n\n// Hide the splashscreen\nimport SplashScreen from \"react-native-splash-screen\";\nSplashScreen.hide();\n\n// enables knobs for all stories\naddDecorator(withKnobs);\n// import stories\nconfigure(() => {\n  require(\"./stories\");\n}, module);\n\n// Refer to https://github.com/storybookjs/storybook/tree/master/app/react-native#start-command-parameters\n// To find allowed options for getStorybookUI\nconst StorybookUIRoot = getStorybookUI({\n  host: Platform.OS === \"android\" ? \"10.0.2.2\" : \"0.0.0.0\",\n  asyncStorage: require(\"@react-native-async-storage/async-storage\").default,\n});\n\n// If you are using React Native vanilla and after installation you don't see your app name here, write it manually.\n// If you use Expo you should remove this line.\nAppRegistry.registerComponent(\"%APP_NAME%\", () => StorybookUIRoot);\n\nexport default StorybookUIRoot;\n"
  },
  {
    "path": "pilaf/storybook/rn-addons.ts",
    "content": "import \"@storybook/addon-ondevice-actions/register\";\nimport \"@storybook/addon-ondevice-knobs/register\";\n"
  },
  {
    "path": "pilaf/storybook/stories/Button.stories.tsx",
    "content": "// the boolean knob renders a switch which lets you toggle a value between true or false\n// you call it like boolean(\"name here\", default_value)\nimport { boolean, radios, text } from \"@storybook/addon-knobs\";\nimport { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { View } from \"react-native\";\nimport { Button } from \"../../src/components/buttons/Button\";\nimport { colors } from \"../../src/constants/dogeStyle\";\nimport CenterView from \"./CenterView\";\n\nconst buttonStories = storiesOf(\"Button\", module);\n\n// lets storybook know to show the knobs addon for this story\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <View\n    style={{\n      display: \"flex\",\n      flex: 1,\n      justifyContent: \"center\",\n      backgroundColor: colors.primary900,\n    }}\n  >\n    <Button\n      iconSrc={\n        boolean(\"Example icon\", false)\n          ? require(\"../../src/assets/images/dogecoin.png\")\n          : undefined\n      }\n      title={text(\"Title\", \"New Room\")}\n      disabled={boolean(\"Disabled\", false)}\n      loading={boolean(\"Loading\", false)}\n      color={radios(\n        \"Color\",\n        {\n          primary: \"primary\",\n          secondary: \"secondary\",\n        },\n        \"primary\"\n      )}\n      size={radios(\n        \"Size\",\n        {\n          big: \"big\",\n          small: \"small\",\n        },\n        \"small\"\n      )}\n    />\n  </View>\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/CenterView/index.tsx",
    "content": "import PropTypes from \"prop-types\";\nimport React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\nimport { colors } from \"../../../src/constants/dogeStyle\";\n\nexport default function CenterView({ children }) {\n  return <View style={styles.main}>{children}</View>;\n}\n\nCenterView.defaultProps = {\n  children: null,\n};\n\nCenterView.propTypes = {\n  children: PropTypes.node,\n};\n\nconst styles = StyleSheet.create({\n  main: {\n    flex: 1,\n    justifyContent: \"center\",\n    //alignItems: \"center\",\n    backgroundColor: colors.primary900,\n  },\n});\n"
  },
  {
    "path": "pilaf/storybook/stories/FeaturedRoomCard.stories.tsx",
    "content": "import { number, text } from \"@storybook/addon-knobs\";\nimport { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { ScrollView, Text } from \"react-native\";\nimport { FeaturedRoomCard } from \"../../src/components/FeaturedRoomCard\";\nimport { Tag } from \"../../src/components/Tag\";\nimport { smallBold } from \"../../src/constants/dogeStyle\";\nimport CenterView from \"./CenterView\";\n\nconst featuredRoomCardStories = storiesOf(\"FeaturedRoomCard\", module);\n\nfeaturedRoomCardStories.addDecorator((getStory) => (\n  <CenterView>{getStory()}</CenterView>\n));\n\nfeaturedRoomCardStories.add(\"Main\", () => (\n  <ScrollView style={{ flex: 1, padding: 20 }}>\n    <FeaturedRoomCard\n      title={text(\"title\", \"Starting your dream business in times of Covid\")}\n      subtitle={text(\"subtitle\", \"Marcus Bloch, Don Velez\")}\n      listeners={number(\"listeners\", 400)}\n      avatarSrcs={[\n        require(\"../../src/assets/images/100.png\"),\n        require(\"../../src/assets/images/100.png\"),\n        require(\"../../src/assets/images/100.png\"),\n      ]}\n      tags={[\n        <Tag style={{ marginRight: 10 }} key={\"trending\"} glow>\n          <Text style={{ ...smallBold }}>🔥 Trending</Text>\n        </Tag>,\n        <Tag style={{ marginRight: 10 }} key={\"business-1\"}>\n          <Text style={{ ...smallBold }}>#Trending</Text>\n        </Tag>,\n        <Tag style={{ marginRight: 10 }} key={\"business-2\"}>\n          <Text style={{ ...smallBold }}>#Business</Text>\n        </Tag>,\n        <Tag style={{ marginRight: 10 }} key={\"business-3\"}>\n          <Text style={{ ...smallBold }}>#Business</Text>\n        </Tag>,\n      ]}\n    />\n  </ScrollView>\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/FollowNotification.stories.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport CenterView from \"./CenterView\";\nimport { FollowNotification } from \"../../src/components/notifications/FollowNotification\";\nimport { boolean, text } from \"@storybook/addon-knobs\";\n\nconst buttonStories = storiesOf(\"FollowNotification\", module);\n\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <FollowNotification\n    username={text(\"UserName\", \"DrMadTurkey\")}\n    userAvatarSrc={require(\"../../src/assets/images/100.png\")}\n    time={\"now\"}\n    isOnline={boolean(\"isOnline\", false)}\n    following={boolean(\"Following\", false)}\n  />\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/GenericNotification.stories.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { Text } from \"react-native\";\nimport CenterView from \"./CenterView\";\nimport { GenericNotification } from \"../../src/components/notifications/GenericNotification\";\nimport { paragraph } from \"../../src/constants/dogeStyle\";\n\nconst buttonStories = storiesOf(\"GenericNotification\", module);\n\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <GenericNotification\n    notificationMsg={\n      <Text style={{ ...paragraph }} numberOfLines={1}>\n        General notification message a bit to long if you don't mind\n      </Text>\n    }\n    time={\"now\"}\n  />\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/LiveNotification.stories.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { LiveNotification } from \"../../src/components/notifications/LiveNotification\";\nimport CenterView from \"./CenterView\";\n\nconst buttonStories = storiesOf(\"LiveNotification\", module);\n\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <LiveNotification username={\"DrMadTurkey\"} time={\"now\"} joined={true} />\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/Message.stories.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { MessageElement } from \"../../src/components/MessageElement\";\nimport CenterView from \"./CenterView\";\n\nconst buttonStories = storiesOf(\"MessageElement\", module);\n\n// lets storybook know to show the knobs addon for this story\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nconst user = {\n  avatar: require(\"../../src/assets/images/100.png\"),\n  username: \"TerryOwen\",\n  isOnline: true,\n};\n\nconst msg = {\n  text:\n    \"Hey! I really liked your room, but would like to contribute to dogehouse\",\n  ts: 1615116474,\n};\n\nbuttonStories.add(\"Main\", () => <MessageElement user={user} msg={msg} />);\n"
  },
  {
    "path": "pilaf/storybook/stories/MultipleUserAvatar.stories.tsx",
    "content": "// the boolean knob renders a switch which lets you toggle a value between true or false\n// you call it like boolean(\"name here\", default_value)\nimport { radios } from \"@storybook/addon-knobs\";\nimport { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { MultipleUserAvatar } from \"../../src/components/avatars/MultipleUserAvatar\";\nimport CenterView from \"./CenterView\";\n\nconst buttonStories = storiesOf(\"MultipleUserAvatar\", module);\n\n// lets storybook know to show the knobs addon for this story\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <MultipleUserAvatar\n    srcArray={[\n      require(\"../../src/assets/images/100.png\"),\n      require(\"../../src/assets/images/100.png\"),\n      require(\"../../src/assets/images/100.png\"),\n    ]}\n    size={radios(\n      \"Size\",\n      {\n        default: \"default\",\n        sm: \"sm\",\n        xs: \"xs\",\n      },\n      \"default\"\n    )}\n  />\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/NewRoomNotification.stories.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport CenterView from \"./CenterView\";\nimport { NewRoomNotification } from \"../../src/components/notifications/NewRoomNotification\";\n\nconst buttonStories = storiesOf(\"NewRoomNotification\", module);\n\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <NewRoomNotification username={\"DrMadTurkey\"} time={\"now\"} joined={true} />\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/SingleUserAvatar.stories.tsx",
    "content": "// the boolean knob renders a switch which lets you toggle a value between true or false\n// you call it like boolean(\"name here\", default_value)\nimport { boolean, radios } from \"@storybook/addon-knobs\";\nimport { storiesOf } from \"@storybook/react-native\";\nimport React from \"react\";\nimport { SingleUserAvatar } from \"../../src/components/avatars/SingleUserAvatar\";\nimport CenterView from \"./CenterView\";\n\nconst buttonStories = storiesOf(\"SingleUserAvatar\", module);\n\n// lets storybook know to show the knobs addon for this story\nbuttonStories.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>);\n\nbuttonStories.add(\"Main\", () => (\n  <SingleUserAvatar\n    isOnline={boolean(\"is online\", false)}\n    src={require(\"../../src/assets/images/100.png\")}\n    size={radios(\n      \"Size\",\n      {\n        default: \"default\",\n        sm: \"sm\",\n        m: \"md\",\n        xxs: \"xxs\",\n        xs: \"xs\",\n      },\n      \"default\"\n    )}\n  />\n));\n"
  },
  {
    "path": "pilaf/storybook/stories/index.ts",
    "content": "import \"./Button.stories\";\nimport \"./FeaturedRoomCard.stories\";\nimport \"./Message.stories\";\nimport \"./SingleUserAvatar.stories\";\nimport \"./MultipleUserAvatar.stories\";\nimport \"./NewRoomNotification.stories\";\nimport \"./LiveNotification.stories\";\nimport \"./GenericNotification.stories\";\nimport \"./FollowNotification.stories\";\n"
  },
  {
    "path": "pilaf/template.config.js",
    "content": "module.exports = {\n  placeholderName: \"rice\",\n  templateDir: \"./template\",\n};\n"
  },
  {
    "path": "pilaf/tokens.ts",
    "content": "export const accessToken = undefined;\nexport const refreshToken = undefined;\n"
  },
  {
    "path": "run.ps1",
    "content": "$env = $args[0]\n$cmd = $args | Select-Object -Skip 1\ndocker-compose -f docker-compose.yml -f docker-compose.$env.yml -f docker-compose.config.yml $cmd"
  },
  {
    "path": "run.sh",
    "content": "#!/usr/bin/env bash\ndocker-compose -f docker-compose.yml -f docker-compose.$1.yml ${@:2}\n"
  },
  {
    "path": "scripts/trigger-electron-deploy.sh",
    "content": "#!/bin/bash\n\ngit add -A\ngit commit -m \"trigger electron deploy\"\n\n# #get highest tag number\n# VERSION=`git describe --abbrev=0 --tags`\n\n# #replace . with space so can split into an array\n# VERSION_BITS=(${VERSION//./ })\n\n# #get number parts and increase last one by 1\n# VNUM1=${VERSION_BITS[0]}\n# VNUM2=${VERSION_BITS[1]}\n# VNUM3=${VERSION_BITS[2]}\n# VNUM3=$((VNUM3+2))\n\n# #create new tag\n# NEW_TAG=\"$VNUM1.$VNUM2.$VNUM3\"\n\n# echo \"Updating $VERSION to $NEW_TAG\"\n\n# git tag v1.0.1\n\ngit checkout prod\ngit merge staging\ngit push origin prod\ngit checkout staging"
  },
  {
    "path": "shawarma/.dockerignore",
    "content": "node_modules\ndist"
  },
  {
    "path": "shawarma/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\"eslint:recommended\"],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 12,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint/eslint-plugin\"],\n  \"rules\": {\n    \"no-unused-vars\": \"off\",\n    \"no-empty\": \"off\"\n  }\n}\n"
  },
  {
    "path": "shawarma/Dockerfile",
    "content": "\nFROM node:14-alpine\nWORKDIR /usr/src/app\nRUN apk add --update alpine-sdk && apk add linux-headers\nRUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python\n\nCOPY package.json package-lock.json ./\n\nRUN npm i\n\nCOPY . .\n\nRUN npm run build\n\nENV NODE_ENV production\nCMD [ \"node\", \"dist/index.js\" ]\nUSER node"
  },
  {
    "path": "shawarma/README.md",
    "content": "# Shawarma - Voice Server\n\n## Running in development\n\n1. Install all dependencies `yarn`\n2. Build Shawarma `yarn build`\n3. Run Shawarma `yarn dev`\n"
  },
  {
    "path": "shawarma/deploy.sh",
    "content": "#!/bin/bash\nset -e\n\nscp -r src package.json tsconfig.json doge-vc:~/voice-server\nssh doge-vc \"source ~/.nvm/nvm.sh ; nvm use 14 && cd voice-server && yarn install && yarn build && pm2 restart dist/index.js\"\n# scp -r src package.json tsconfig.json doge-vc1:~/voice-server\n# ssh doge-vc1 \"source ~/.nvm/nvm.sh ; nvm use 14 && cd voice-server && npm i && npm run build && pm2 restart dist/index.js\""
  },
  {
    "path": "shawarma/package.json",
    "content": "{\n  \"name\": \"shawarma\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"watch\": \"tsc -w\",\n    \"start\": \"node dist/index.js\",\n    \"build\": \"tsc\",\n    \"dev\": \"cross-env DEBUG=shawarma:* nodemon dist/index.js\",\n    \"dev2\": \"cross-env QUEUE_ID=1 DEBUG=shawarma:* nodemon dist/index.js\",\n    \"dev:ts\": \"cross-env DEBUG=shawarma:*  ts-node src\",\n    \"dev2:ts\": \"cross-env QUEUE_ID=1  DEBUG=shawarma:*  ts-node src\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@sentry/node\": \"^6.3.3\",\n    \"amqplib\": \"^0.7.1\",\n    \"cors\": \"^2.8.5\",\n    \"debug\": \"^4.3.2\",\n    \"dotenv\": \"^8.2.0\",\n    \"dotenv-safe\": \"^8.2.0\",\n    \"express\": \"^4.17.1\",\n    \"jsonwebtoken\": \"^8.5.1\",\n    \"mediasoup\": \"^3.7.2\",\n    \"ws\": \"^7.4.2\"\n  },\n  \"devDependencies\": {\n    \"@types/amqplib\": \"^0.5.17\",\n    \"@types/cors\": \"^2.8.9\",\n    \"@types/debug\": \"^4.1.5\",\n    \"@types/express\": \"^4.17.11\",\n    \"@types/jsonwebtoken\": \"^8.5.0\",\n    \"@types/node\": \"^14.14.21\",\n    \"@types/pino\": \"^6.3.5\",\n    \"@types/protoo-server\": \"^4.0.1\",\n    \"@types/ws\": \"^7.4.0\",\n    \"cross-env\": \"^7.0.3\",\n    \"nodemon\": \"^2.0.7\",\n    \"ts-node\": \"9.1.1\",\n    \"typescript\": \"^4.1.3\"\n  }\n}"
  },
  {
    "path": "shawarma/src/MyPeer.ts",
    "content": "import { Consumer, Producer, Transport } from \"mediasoup/lib/types\";\n\nexport type MyPeer = {\n  sendTransport: Transport | null;\n  recvTransport: Transport | null;\n  producer: Producer | null;\n  consumers: Consumer[];\n};\n"
  },
  {
    "path": "shawarma/src/MyRoomState.ts",
    "content": "import { Router, Worker } from \"mediasoup/lib/types\";\nimport { MyPeer } from \"./MyPeer\";\n\nexport type Then<T> = T extends PromiseLike<infer U> ? U : T;\n\nexport type MyRoomState = Record<string, MyPeer>;\n\nexport type MyRooms = Record<\n  string,\n  { worker: Worker; router: Router; state: MyRoomState }\n>;\n"
  },
  {
    "path": "shawarma/src/config.ts",
    "content": "import {\n  RtpCodecCapability,\n  TransportListenIp,\n  WorkerLogTag,\n} from \"mediasoup/lib/types\";\n\nexport const config = {\n  // http server ip, port, and peer timeout constant\n  //\n  httpIp: \"0.0.0.0\",\n  httpPort: 3000,\n  httpPeerStale: 360000,\n\n  mediasoup: {\n    worker: {\n      rtcMinPort: 40000,\n      rtcMaxPort: 49999,\n      logLevel: \"debug\",\n      logTags: [\n        \"info\",\n        \"ice\",\n        \"dtls\",\n        \"rtp\",\n        \"srtp\",\n        \"rtcp\",\n        // 'rtx',\n        // 'bwe',\n        // 'score',\n        // 'simulcast',\n        // 'svc'\n      ] as WorkerLogTag[],\n    },\n    router: {\n      mediaCodecs: [\n        {\n          kind: \"audio\",\n          mimeType: \"audio/opus\",\n          clockRate: 48000,\n          channels: 2,\n        },\n      ] as RtpCodecCapability[],\n    },\n\n    // rtp listenIps are the most important thing, below. you'll need\n    // to set these appropriately for your network for the demo to\n    // run anywhere but on localhost\n    webRtcTransport: {\n      listenIps: [\n        {\n          ip: process.env.WEBRTC_LISTEN_IP || \"192.168.1.165\",\n          announcedIp: process.env.A_IP || undefined,\n        },\n        // { ip: \"192.168.42.68\", announcedIp: null },\n        // { ip: '10.10.23.101', announcedIp: null },\n      ] as TransportListenIp[],\n      initialAvailableOutgoingBitrate: 800000,\n    },\n  },\n} as const;\n"
  },
  {
    "path": "shawarma/src/constants.ts",
    "content": "export const __prod__ = process.env.NODE_ENV === \"production\";\n"
  },
  {
    "path": "shawarma/src/index.ts",
    "content": "import { main } from \"./main\";\n\nmain();\n"
  },
  {
    "path": "shawarma/src/main.ts",
    "content": "import \"dotenv/config\";\nimport debugModule from \"debug\";\nimport { Router, Worker } from \"mediasoup/lib/types\";\nimport * as Sentry from \"@sentry/node\";\nimport { MyRooms } from \"./MyRoomState\";\nimport { closePeer } from \"./utils/closePeer\";\nimport { createConsumer } from \"./utils/createConsumer\";\nimport { createTransport, transportToOptions } from \"./utils/createTransport\";\nimport { deleteRoom } from \"./utils/deleteRoom\";\nimport { startMediasoup } from \"./utils/startMediasoup\";\nimport { HandlerMap, startRabbit } from \"./utils/startRabbit\";\n\nconst log = debugModule(\"shawarma:index\");\nconst errLog = debugModule(\"shawarma:ERROR\");\n\nconst rooms: MyRooms = {};\n\nexport async function main() {\n  if (process.env.SENTRY_DNS) {\n    Sentry.init({\n      dsn: process.env.SENTRY_DNS,\n      enabled: !!process.env.SENTRY_DNS,\n    });\n  }\n  // start mediasoup\n  console.log(\"starting mediasoup\");\n  let workers: {\n    worker: Worker;\n    router: Router;\n  }[];\n  try {\n    workers = await startMediasoup();\n  } catch (err) {\n    console.log(err);\n    throw err;\n  }\n  let workerIdx = 0;\n\n  const getNextWorker = () => {\n    const w = workers[workerIdx];\n    workerIdx++;\n    workerIdx %= workers.length;\n    return w;\n  };\n\n  const createRoom = () => {\n    const { worker, router } = getNextWorker();\n\n    return { worker, router, state: {} };\n  };\n\n  await startRabbit({\n    \"remove-speaker\": ({ roomId, peerId }) => {\n      if (roomId in rooms) {\n        const peer = rooms[roomId].state[peerId];\n        peer?.producer?.close();\n        peer?.sendTransport?.close();\n      }\n    },\n    [\"destroy-room\"]: ({ roomId }) => {\n      if (roomId in rooms) {\n        for (const peer of Object.values(rooms[roomId].state)) {\n          closePeer(peer);\n        }\n        deleteRoom(roomId, rooms);\n      }\n    },\n    [\"close-peer\"]: async ({ roomId, peerId, kicked }, uid, send) => {\n      if (roomId in rooms) {\n        if (peerId in rooms[roomId].state) {\n          closePeer(rooms[roomId].state[peerId]);\n          delete rooms[roomId].state[peerId];\n        }\n        if (Object.keys(rooms[roomId].state).length === 0) {\n          deleteRoom(roomId, rooms);\n        }\n        send({ uid, op: \"you_left_room\", d: { roomId, kicked: !!kicked } });\n      }\n    },\n    [\"@get-recv-tracks\"]: async (\n      { roomId, peerId: myPeerId, rtpCapabilities },\n      uid,\n      send,\n      errBack\n    ) => {\n      if (!rooms[roomId]?.state[myPeerId]?.recvTransport) {\n        errBack();\n        return;\n      }\n\n      const { state, router } = rooms[roomId];\n      const transport = state[myPeerId].recvTransport;\n      if (!transport) {\n        errBack();\n        return;\n      }\n\n      const consumerParametersArr = [];\n\n      for (const theirPeerId of Object.keys(state)) {\n        const peerState = state[theirPeerId];\n        if (theirPeerId === myPeerId || !peerState || !peerState.producer) {\n          continue;\n        }\n        try {\n          const { producer } = peerState;\n          consumerParametersArr.push(\n            await createConsumer(\n              router,\n              producer,\n              rtpCapabilities,\n              transport,\n              myPeerId,\n              state[theirPeerId]\n            )\n          );\n        } catch (e) {\n          errLog(e.message);\n          continue;\n        }\n      }\n\n      send({\n        op: \"@get-recv-tracks-done\",\n        uid,\n        d: { consumerParametersArr, roomId },\n      });\n    },\n    [\"@send-track\"]: async (\n      {\n        roomId,\n        transportId,\n        direction,\n        peerId: myPeerId,\n        kind,\n        rtpParameters,\n        rtpCapabilities,\n        paused,\n        appData,\n      },\n      uid,\n      send,\n      errBack\n    ) => {\n      if (!(roomId in rooms)) {\n        errBack();\n        return;\n      }\n      const { state } = rooms[roomId];\n      const { sendTransport, producer: previousProducer, consumers } = state[\n        myPeerId\n      ];\n      const transport = sendTransport;\n\n      if (!transport) {\n        errBack();\n        return;\n      }\n      try {\n        if (previousProducer) {\n          previousProducer.close();\n          consumers.forEach((c) => c.close());\n          // @todo give some time for frontends to get update, but this can be removed\n          send({\n            rid: roomId,\n            op: \"close_consumer\",\n            d: { producerId: previousProducer.id, roomId },\n          });\n        }\n\n        const producer = await transport.produce({\n          kind,\n          rtpParameters,\n          paused,\n          appData: { ...appData, peerId: myPeerId, transportId },\n        });\n\n        rooms[roomId].state[myPeerId].producer = producer;\n        for (const theirPeerId of Object.keys(state)) {\n          if (theirPeerId === myPeerId) {\n            continue;\n          }\n          const peerTransport = state[theirPeerId]?.recvTransport;\n          if (!peerTransport) {\n            continue;\n          }\n          try {\n            const d = await createConsumer(\n              rooms[roomId].router,\n              producer,\n              rtpCapabilities,\n              peerTransport,\n              myPeerId,\n              state[theirPeerId]\n            );\n            send({\n              uid: theirPeerId,\n              op: \"new-peer-speaker\",\n              d: { ...d, roomId },\n            });\n          } catch (e) {\n            errLog(e.message);\n          }\n        }\n        send({\n          op: `@send-track-${direction}-done` as const,\n          uid,\n          d: {\n            id: producer.id,\n            roomId,\n          },\n        });\n      } catch (e) {\n        send({\n          op: `@send-track-${direction}-done` as const,\n          uid,\n          d: {\n            error: e.message,\n            roomId,\n          },\n        });\n        send({\n          op: \"error\",\n          d: \"error connecting to voice server | \" + e.message,\n          uid,\n        });\n        return;\n      }\n    },\n    [\"@connect-transport\"]: async (\n      { roomId, dtlsParameters, peerId, direction },\n      uid,\n      send,\n      errBack\n    ) => {\n      if (!rooms[roomId]?.state[peerId]) {\n        errBack();\n        return;\n      }\n      const { state } = rooms[roomId];\n      const transport =\n        direction === \"recv\"\n          ? state[peerId].recvTransport\n          : state[peerId].sendTransport;\n\n      if (!transport) {\n        errBack();\n        return;\n      }\n\n      log(\"connect-transport\", peerId, transport.appData);\n\n      try {\n        await transport.connect({ dtlsParameters });\n      } catch (e) {\n        console.log(e);\n        send({\n          op: `@connect-transport-${direction}-done` as const,\n          uid,\n          d: { error: e.message, roomId },\n        });\n        send({\n          op: \"error\",\n          d: \"error connecting to voice server | \" + e.message,\n          uid,\n        });\n        return;\n      }\n      send({\n        op: `@connect-transport-${direction}-done` as const,\n        uid,\n        d: { roomId },\n      });\n    },\n    [\"create-room\"]: async ({ roomId }, uid, send) => {\n      if (!(roomId in rooms)) {\n        rooms[roomId] = createRoom();\n      }\n      send({ op: \"room-created\", d: { roomId }, uid });\n    },\n    [\"add-speaker\"]: async ({ roomId, peerId }, uid, send, errBack) => {\n      if (!rooms[roomId]?.state[peerId]) {\n        errBack();\n        return;\n      }\n      log(\"add-speaker\", peerId);\n\n      const { router } = rooms[roomId];\n      const sendTransport = await createTransport(\"send\", router, peerId);\n      rooms[roomId].state[peerId].sendTransport?.close();\n      rooms[roomId].state[peerId].sendTransport = sendTransport;\n\n      send({\n        op: \"you-are-now-a-speaker\",\n        d: {\n          sendTransportOptions: transportToOptions(sendTransport),\n          roomId,\n        },\n        uid,\n      });\n    },\n    [\"join-as-speaker\"]: async ({ roomId, peerId }, uid, send) => {\n      if (!(roomId in rooms)) {\n        rooms[roomId] = createRoom();\n      }\n      log(\"join-as-new-peer\", peerId);\n\n      const { state, router } = rooms[roomId];\n      const [recvTransport, sendTransport] = await Promise.all([\n        createTransport(\"recv\", router, peerId),\n        createTransport(\"send\", router, peerId),\n      ]);\n      if (state[peerId]) {\n        closePeer(state[peerId]);\n      }\n      rooms[roomId].state[peerId] = {\n        recvTransport: recvTransport,\n        sendTransport: sendTransport,\n        consumers: [],\n        producer: null,\n      };\n\n      send({\n        op: \"you-joined-as-speaker\",\n        d: {\n          roomId,\n          peerId,\n          routerRtpCapabilities: rooms[roomId].router.rtpCapabilities,\n          recvTransportOptions: transportToOptions(recvTransport),\n          sendTransportOptions: transportToOptions(sendTransport),\n        },\n        uid,\n      });\n    },\n    [\"join-as-new-peer\"]: async ({ roomId, peerId }, uid, send) => {\n      if (!(roomId in rooms)) {\n        rooms[roomId] = createRoom();\n      }\n      log(\"join-as-new-peer\", peerId);\n      const { state, router } = rooms[roomId];\n      const recvTransport = await createTransport(\"recv\", router, peerId);\n      if (state[peerId]) {\n        closePeer(state[peerId]);\n      }\n\n      rooms[roomId].state[peerId] = {\n        recvTransport,\n        consumers: [],\n        producer: null,\n        sendTransport: null,\n      };\n\n      send({\n        op: \"you-joined-as-peer\",\n        d: {\n          roomId,\n          peerId,\n          routerRtpCapabilities: rooms[roomId].router.rtpCapabilities,\n          recvTransportOptions: transportToOptions(recvTransport),\n        },\n        uid,\n      });\n    },\n  } as HandlerMap);\n}\n"
  },
  {
    "path": "shawarma/src/types/env.d.ts",
    "content": "declare namespace NodeJS {\n  export interface ProcessEnv {\n    ACCESS_TOKEN_SECRET: string;\n  }\n}\n"
  },
  {
    "path": "shawarma/src/types/index.ts",
    "content": "export type VoiceSendDirection = \"recv\" | \"send\";"
  },
  {
    "path": "shawarma/src/utils/closePeer.ts",
    "content": "import { MyPeer } from \"../MyPeer\";\n\nexport const closePeer = (state: MyPeer) => {\n  state.producer?.close();\n  state.recvTransport?.close();\n  state.sendTransport?.close();\n  state.consumers.forEach((c) => c.close());\n};\n"
  },
  {
    "path": "shawarma/src/utils/createConsumer.ts",
    "content": "import {\n  ConsumerType,\n  Producer,\n  Router,\n  RtpCapabilities,\n  RtpParameters,\n  Transport,\n} from \"mediasoup/lib/types\";\nimport { MyPeer } from \"../MyPeer\";\n\nexport const createConsumer = async (\n  router: Router,\n  producer: Producer,\n  rtpCapabilities: RtpCapabilities,\n  transport: Transport,\n  peerId: string,\n  peerConsuming: MyPeer\n): Promise<Consumer> => {\n  if (!router.canConsume({ producerId: producer.id, rtpCapabilities })) {\n    throw new Error(\n      `recv-track: client cannot consume ${producer.appData.peerId}`\n    );\n  }\n\n  const consumer = await transport.consume({\n    producerId: producer.id,\n    rtpCapabilities,\n    paused: false, // see note above about always starting paused\n    appData: { peerId, mediaPeerId: producer.appData.peerId },\n  });\n\n  // consumer.on(\"transportclose\", () => {\n  //   log(`consumer's transport closed`, consumer.id);\n  //   closeConsumer(consumer, peerConsuming);\n  // });\n  // consumer.on(\"producerclose\", () => {\n  //   log(`consumer's producer closed`, consumer.id);\n  //   closeConsumer(consumer, peerConsuming);\n  // });\n\n  peerConsuming.consumers.push(consumer);\n\n  return {\n    peerId: producer.appData.peerId,\n    consumerParameters: {\n      producerId: producer.id,\n      id: consumer.id,\n      kind: consumer.kind,\n      rtpParameters: consumer.rtpParameters,\n      type: consumer.type,\n      producerPaused: consumer.producerPaused,\n    },\n  };\n};\n\nexport interface Consumer {\n  peerId: string;\n  consumerParameters: {\n    producerId: string;\n    id: string;\n    kind: string;\n    rtpParameters: RtpParameters;\n    type: ConsumerType;\n    producerPaused: boolean;\n  };\n};\n"
  },
  {
    "path": "shawarma/src/utils/createTransport.ts",
    "content": "import debugModule from \"debug\";\nimport { Router, WebRtcTransport } from \"mediasoup/lib/types\";\nimport { VoiceSendDirection } from \"src/types\";\nimport { config } from \"../config\";\n\nconst log = debugModule(\"shawarma:create-transport\");\n\n\nexport const transportToOptions = ({\n  id,\n  iceParameters,\n  iceCandidates,\n  dtlsParameters,\n}: WebRtcTransport) => ({ id, iceParameters, iceCandidates, dtlsParameters });\n\nexport type TransportOptions = ReturnType<typeof transportToOptions>;\n\nexport const createTransport = async (\n  direction: VoiceSendDirection,\n  router: Router,\n  peerId: string\n) => {\n  log(\"create-transport\", direction);\n  const {\n    listenIps,\n    initialAvailableOutgoingBitrate,\n  } = config.mediasoup.webRtcTransport;\n\n  const transport = await router.createWebRtcTransport({\n    listenIps: listenIps,\n    enableUdp: true,\n    enableTcp: true,\n    preferUdp: true,\n    initialAvailableOutgoingBitrate: initialAvailableOutgoingBitrate,\n    appData: { peerId, clientDirection: direction },\n  });\n  return transport;\n};\n"
  },
  {
    "path": "shawarma/src/utils/deleteRoom.ts",
    "content": "import { MyRooms } from \"../MyRoomState\";\n\nexport const deleteRoom = (roomId: string, rooms: MyRooms) => {\n  if (!(roomId in rooms)) {\n    return;\n  }\n\n  delete rooms[roomId];\n};\n"
  },
  {
    "path": "shawarma/src/utils/startMediasoup.ts",
    "content": "import * as mediasoup from \"mediasoup\";\nimport { Router, Worker } from \"mediasoup/lib/types\";\nimport os from \"os\";\nimport { config } from \"../config\";\n\nexport async function startMediasoup() {\n  const workers: Array<{\n    worker: Worker;\n    router: Router;\n  }> = [];\n  for (let i = 0; i < Object.keys(os.cpus()).length; i++) {\n    let worker = await mediasoup.createWorker({\n      logLevel: config.mediasoup.worker.logLevel,\n      logTags: config.mediasoup.worker.logTags,\n      rtcMinPort: config.mediasoup.worker.rtcMinPort,\n      rtcMaxPort: config.mediasoup.worker.rtcMaxPort,\n    });\n\n    worker.on(\"died\", () => {\n      console.error(\"mediasoup worker died (this should never happen)\");\n      process.exit(1);\n    });\n\n    const mediaCodecs = config.mediasoup.router.mediaCodecs;\n    const router = await worker.createRouter({ mediaCodecs });\n\n    workers.push({ worker, router });\n  }\n\n  return workers;\n}\n"
  },
  {
    "path": "shawarma/src/utils/startRabbit.ts",
    "content": "import amqp, { Connection } from \"amqplib\";\nimport * as Sentry from \"@sentry/node\";\nimport {\n  DtlsParameters,\n  MediaKind,\n  RtpCapabilities,\n  RtpParameters,\n} from \"mediasoup/lib/types\";\nimport { VoiceSendDirection } from \"src/types\";\nimport { TransportOptions } from \"./createTransport\";\nimport { Consumer } from \"./createConsumer\";\n\nconst retryInterval = 5000;\nexport interface HandlerDataMap {\n  \"remove-speaker\": { roomId: string; peerId: string };\n  \"destroy-room\": { roomId: string };\n  \"close-peer\": { roomId: string; peerId: string; kicked?: boolean };\n  \"@get-recv-tracks\": {\n    roomId: string;\n    peerId: string;\n    rtpCapabilities: RtpCapabilities;\n  };\n  \"@send-track\": {\n    roomId: string;\n    peerId: string;\n    transportId: string;\n    direction: VoiceSendDirection;\n    paused: boolean;\n    kind: MediaKind;\n    rtpParameters: RtpParameters;\n    rtpCapabilities: RtpCapabilities;\n    appData: any;\n  };\n  \"@connect-transport\": {\n    roomId: string;\n    dtlsParameters: DtlsParameters;\n    peerId: string;\n    direction: VoiceSendDirection;\n  };\n  \"create-room\": {\n    roomId: string;\n  };\n  \"add-speaker\": {\n    roomId: string;\n    peerId: string;\n  };\n  \"join-as-speaker\": {\n    roomId: string;\n    peerId: string;\n  };\n  \"join-as-new-peer\": {\n    roomId: string;\n    peerId: string;\n  };\n}\n\nexport type HandlerMap = {\n  [Key in keyof HandlerDataMap]: (\n    d: HandlerDataMap[Key],\n    uid: string,\n    send: <Key extends keyof OutgoingMessageDataMap>(\n      obj: OutgoingMessage<Key>\n    ) => void,\n    errBack: () => void\n  ) => void;\n};\n\ntype SendTrackDoneOperationName = `@send-track-${VoiceSendDirection}-done`;\ntype ConnectTransportDoneOperationName = `@connect-transport-${VoiceSendDirection}-done`;\n\ntype OutgoingMessageDataMap = {\n  \"you-joined-as-speaker\": {\n    roomId: string;\n    peerId: string;\n    routerRtpCapabilities: RtpCapabilities;\n    recvTransportOptions: TransportOptions;\n    sendTransportOptions: TransportOptions;\n  };\n  error: string;\n  \"room-created\": {\n    roomId: string;\n  };\n  \"@get-recv-tracks-done\": {\n    consumerParametersArr: Consumer[];\n    roomId: string;\n  };\n  close_consumer: {\n    producerId: string;\n    roomId: string;\n  };\n  \"new-peer-speaker\": {\n    roomId: string;\n  } & Consumer;\n  you_left_room: {\n    roomId: string;\n    kicked: boolean;\n  };\n  \"you-are-now-a-speaker\": {\n    sendTransportOptions: TransportOptions;\n    roomId: string;\n  };\n  \"you-joined-as-peer\": {\n    roomId: string;\n    peerId: string;\n    routerRtpCapabilities: RtpCapabilities;\n    recvTransportOptions: TransportOptions;\n  };\n} & {\n  [Key in SendTrackDoneOperationName]: {\n    error?: string;\n    id?: string;\n    roomId: string;\n  };\n} &\n  {\n    [Key in ConnectTransportDoneOperationName]: {\n      error?: string;\n      roomId: string;\n    };\n  };\n\ntype OutgoingMessage<Key extends keyof OutgoingMessageDataMap> = {\n  op: Key;\n  d: OutgoingMessageDataMap[Key];\n} & ({ uid: string } | { rid: string });\ninterface IncomingChannelMessageData<Key extends keyof HandlerMap> {\n  op: Key;\n  d: HandlerDataMap[Key];\n  uid: string;\n}\n\nexport let send = <Key extends keyof OutgoingMessageDataMap>(\n  _obj: OutgoingMessage<Key>\n) => {};\n\nexport const startRabbit = async (handler: HandlerMap) => {\n  console.log(\n    \"trying to connect to: \",\n    process.env.RABBITMQ_URL || \"amqp://localhost\"\n  );\n  let conn: Connection;\n  try {\n    conn = await amqp.connect(process.env.RABBITMQ_URL || \"amqp://localhost\");\n  } catch (err) {\n    console.error(\"Unable to connect to RabbitMQ: \", err);\n    setTimeout(async () => await startRabbit(handler), retryInterval);\n    return;\n  }\n  const id = process.env.QUEUE_ID || \"\";\n  console.log(\"rabbit connected \" + id);\n  conn.on(\"close\", async function (err: Error) {\n    console.error(\"Rabbit connection closed with error: \", err);\n    setTimeout(async () => await startRabbit(handler), retryInterval);\n  });\n  const channel = await conn.createChannel();\n  const sendQueue = \"kousa_queue\" + id;\n  const onlineQueue = \"kousa_online_queue\" + id;\n  const receiveQueue = \"shawarma_queue\" + id;\n  console.log(sendQueue, onlineQueue, receiveQueue);\n  await Promise.all([\n    channel.assertQueue(receiveQueue),\n    channel.assertQueue(sendQueue),\n    channel.assertQueue(onlineQueue),\n  ]);\n  send = <Key extends keyof OutgoingMessageDataMap>(\n    obj: OutgoingMessage<Key>\n  ) => {\n    channel.sendToQueue(sendQueue, Buffer.from(JSON.stringify(obj)));\n  };\n  await channel.purgeQueue(receiveQueue);\n  await channel.consume(\n    receiveQueue,\n    async (e) => {\n      const m = e?.content.toString();\n      if (m) {\n        let data: IncomingChannelMessageData<any> | undefined;\n        try {\n          data = JSON.parse(m);\n        } catch {}\n        // console.log(data.op);\n        if (data && data.op && data.op in handler) {\n          const { d: handlerData, op: operation, uid } = data;\n          try {\n            console.log(operation);\n            await handler[operation as keyof HandlerMap](\n              handlerData,\n              uid,\n              send,\n              () => {\n                console.log(operation);\n                send({\n                  op: \"error\",\n                  d:\n                    \"The voice server is probably redeploying, it should reconnect in a few seconds. If not, try refreshing.\",\n                  uid: uid,\n                });\n              }\n            );\n          } catch (err) {\n            console.log(operation, err);\n            Sentry.captureException(err, { extra: { op: operation } });\n          }\n        }\n      }\n    },\n    { noAck: true }\n  );\n  channel.sendToQueue(\n    onlineQueue,\n    Buffer.from(JSON.stringify({ op: \"online\" }))\n  );\n};\n"
  },
  {
    "path": "shawarma/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"dom\", \"es6\", \"es2017\", \"esnext.asynciterable\"],\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"moduleResolution\": \"node\",\n    \"removeComments\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"skipLibCheck\": true,\n    \"strictFunctionTypes\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"resolveJsonModule\": true,\n    \"baseUrl\": \".\"\n  },\n  \"exclude\": [\"node_modules\"],\n  \"include\": [\"./src/**/*.tsx\", \"./src/**/*.ts\"]\n}\n"
  },
  {
    "path": "🐕.🏠",
    "content": "🐕🏠🚀✨\n"
  }
]