diff --git a/.ci/format.sh b/.ci/format.sh index d3b629c3..e1e6c1e4 100755 --- a/.ci/format.sh +++ b/.ci/format.sh @@ -11,5 +11,7 @@ FILES=$(find src -type f -type f \( -iname "*.cpp" -o -iname "*.h" \)) for f in $FILES do - clang-format -i "$f" && git diff --exit-code + clang-format -i "$f" done; + +git diff --exit-code diff --git a/.ci/install.sh b/.ci/install.sh index d8dd67f2..d1bff54b 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -2,14 +2,13 @@ set -ex +if [ "$FLATPAK" ]; then + flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak --noninteractive install --user flathub org.kde.Platform//5.14 + flatpak --noninteractive install --user flathub org.kde.Sdk//5.14 + exit +fi if [ "$TRAVIS_OS_NAME" = "osx" ]; then - brew update - brew install qt5 lmdb clang-format ninja libsodium cmark - brew upgrade boost cmake icu4c || true - - brew tap nlohmann/json - brew install --with-cmake nlohmann_json - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py sudo python get-pip.py @@ -21,34 +20,12 @@ fi if [ "$TRAVIS_OS_NAME" = "linux" ]; then + sudo update-alternatives --install /usr/bin/gcc gcc "/usr/bin/${CC}" 10 + sudo update-alternatives --install /usr/bin/g++ g++ "/usr/bin/${CXX}" 10 - if [ -z "$QT_VERSION" ]; then - QT_VERSION="592" - QT_PKG="59" - fi + sudo update-alternatives --set gcc "/usr/bin/${CC}" + sudo update-alternatives --set g++ "/usr/bin/${CXX}" - wget https://cmake.org/files/v3.12/cmake-3.12.2-Linux-x86_64.sh - sudo sh cmake-3.12.2-Linux-x86_64.sh --skip-license --prefix=/usr/local - - mkdir -p build-libsodium - ( cd build-libsodium - curl -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz -o libsodium-1.0.16.tar.gz - tar xfz libsodium-1.0.16.tar.gz - cd libsodium-1.0.16/ - ./configure && make && make check && sudo make install ) - - sudo add-apt-repository -y ppa:beineri/opt-qt${QT_VERSION}-trusty - # needed for git-lfs, otherwise the follow apt update fails. - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 6B05F25D762E3157 - - # needed for mongodb repository: https://github.com/travis-ci/travis-ci/issues/9037 - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 - - sudo apt update -qq - sudo apt install -qq -y \ - qt${QT_PKG}base \ - qt${QT_PKG}tools \ - qt${QT_PKG}svg \ - qt${QT_PKG}multimedia \ - liblmdb-dev + wget https://cmake.org/files/v3.15/cmake-3.15.5-Linux-x86_64.sh + sudo sh cmake-3.15.5-Linux-x86_64.sh --skip-license --prefix=/usr/local fi diff --git a/.ci/linux/deploy.sh b/.ci/linux/deploy.sh index 403fde14..ff8ef6ca 100755 --- a/.ci/linux/deploy.sh +++ b/.ci/linux/deploy.sh @@ -25,8 +25,8 @@ for iconSize in 16 32 48 64 128 256 512; do done # Only download the file when not already present -if ! [ -f linuxdeployqt-continuous-x86_64.AppImage ] ; then - wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" +if ! [ -f linuxdeployqt-6-x86_64.AppImage ] ; then + wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/6/linuxdeployqt-6-x86_64.AppImage" fi chmod a+x linuxdeployqt*.AppImage @@ -36,7 +36,7 @@ unset LD_LIBRARY_PATH ARCH=$(uname -m) export ARCH -LD_LIBRARY_PATH=$(pwd)/.deps/usr/lib/:$LD_LIBRARY_PATH +LD_LIBRARY_PATH=$(pwd)/.deps/usr/lib/:/usr/local/lib/:$LD_LIBRARY_PATH export LD_LIBRARY_PATH for res in ./linuxdeployqt*.AppImage @@ -44,12 +44,14 @@ do linuxdeployqt=$res done -./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -bundle-non-qt-libs -./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -appimage +./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -bundle-non-qt-libs -qmldir=./resources/qml -appimage chmod +x nheko-*x86_64.AppImage -if [ ! -z "$VERSION" ]; then +mkdir artifacts +cp nheko-*x86_64.AppImage artifacts/ + +if [ -n "$VERSION" ]; then # commented out for now, as AppImage file appears to already contain the version. #mv nheko-*x86_64.AppImage nheko-${VERSION}-x86_64.AppImage echo "nheko-${VERSION}-x86_64.AppImage" diff --git a/.ci/macos/deploy.sh b/.ci/macos/deploy.sh index 45ed13bc..1dc9472d 100755 --- a/.ci/macos/deploy.sh +++ b/.ci/macos/deploy.sh @@ -16,7 +16,7 @@ PATH=/usr/local/opt/qt/bin/:${PATH} mkdir -p nheko.app/Contents/Frameworks find "${ICU_LIB}" -type l -name "*.dylib" -exec cp -a -n {} nheko.app/Contents/Frameworks/ \; || true - sudo macdeployqt nheko.app -dmg -always-overwrite + sudo macdeployqt nheko.app -dmg -always-overwrite -qmldir=../resources/qml/ user=$(id -nu) sudo chown "${user}" nheko.dmg @@ -25,6 +25,8 @@ PATH=/usr/local/opt/qt/bin/:${PATH} dmgbuild -s ./.ci/macos/settings.json "Nheko" nheko.dmg -if [ ! -z "$VERSION" ]; then +if [ -n "$VERSION" ]; then mv nheko.dmg "nheko-${VERSION}.dmg" + mkdir artifacts + cp "nheko-${VERSION}.dmg" artifacts/ fi diff --git a/.ci/script.sh b/.ci/script.sh index cf8b524b..53bb1d9e 100755 --- a/.ci/script.sh +++ b/.ci/script.sh @@ -2,17 +2,25 @@ set -ex +if [ "$FLATPAK" ]; then + mkdir -p build-flatpak + cd build-flatpak + + flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json + flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko 0.7.0-dev + + mkdir ../artifacts + mv nheko-*.flatpak ../artifacts + + exit +fi + if [ "$TRAVIS_OS_NAME" = "linux" ]; then - export CC=${C_COMPILER} - export CXX=${CXX_COMPILER} # make build use all available cores export CMAKE_BUILD_PARALLEL_LEVEL=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) - sudo update-alternatives --install /usr/bin/gcc gcc "/usr/bin/${C_COMPILER}" 10 - sudo update-alternatives --install /usr/bin/g++ g++ "/usr/bin/${CXX_COMPILER}" 10 - - sudo update-alternatives --set gcc "/usr/bin/${C_COMPILER}" - sudo update-alternatives --set g++ "/usr/bin/${CXX_COMPILER}" + export PATH="/usr/local/bin/:${PATH}" + cmake --version fi if [ "$TRAVIS_OS_NAME" = "linux" ]; then @@ -24,27 +32,39 @@ if [ "$TRAVIS_OS_NAME" = "osx" ]; then export CMAKE_PREFIX_PATH=/usr/local/opt/qt5 fi -# Build & install dependencies -cmake -GNinja -Hdeps -B.deps \ - -DUSE_BUNDLED_BOOST="${USE_BUNDLED_BOOST}" \ - -DUSE_BUNDLED_CMARK="${USE_BUNDLED_CMARK}" \ - -DUSE_BUNDLED_JSON="${USE_BUNDLED_JSON}" -cmake --build .deps +mkdir -p .deps/usr .hunter # Build nheko + +if [ "$TRAVIS_OS_NAME" = "osx" ]; then cmake -GNinja -H. -Bbuild \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_INSTALL_PREFIX=.deps/usr + -DCMAKE_INSTALL_PREFIX=.deps/usr \ + -DHUNTER_ROOT=".hunter" \ + -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \ + -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 \ + -DCI_BUILD=ON +else +cmake -GNinja -H. -Bbuild \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=.deps/usr \ + -DHUNTER_ROOT=".hunter" \ + -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \ + -DUSE_BUNDLED_OPENSSL=OFF \ + -DCI_BUILD=ON +fi cmake --build build if [ "$TRAVIS_OS_NAME" = "osx" ]; then make lint; - if [ "$DEPLOYMENT" = 1 ] && [ ! -z "$VERSION" ] ; then + if [ "$DEPLOYMENT" = 1 ] && [ -n "$VERSION" ] ; then make macos-deploy; fi fi -if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$DEPLOYMENT" = 1 ] && [ ! -z "$VERSION" ]; then +if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$DEPLOYMENT" = 1 ] && [ -n "$VERSION" ]; then make linux-deploy; fi diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 706b00cc..fcda66fd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -20,9 +20,9 @@ If you're planning to work on a new feature leave a message on the Matrix room Example for a Japanese translation. - Create a new translation file using the prototype in English - - e.g `cp resources/langs/nheko_en.ts resources/langs/nheko_jp.ts` + - e.g `cp resources/langs/nheko_en.ts resources/langs/nheko_ja.ts` - Open the new translation file and change the line regarding the locale to reflect the current language. - - e.g `` => `` + - e.g `` => `` - Run `make update-translations` to update the translation files with any missing text. - Fill out the translation file (Qt Linguist can make things easier). - Submit a PR! diff --git a/.gitignore b/.gitignore index e9c854d0..6d178679 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,16 @@ -build +/build* tags +cscope* .clang_complete *wintoastlib* +/.ccls-cache +/.exrc +.gdb_history + +# GTAGS +GTAGS +GRTAGS +GPATH # C++ objects and libs @@ -31,6 +40,7 @@ moc_*.cpp qrc_*.cpp ui_*.h *-build-* +/.clangd/ # QtCreator @@ -43,6 +53,10 @@ ui_*.h *.qmlproject.user *.qmlproject.user.* +# Vim +*.swp +*.swo + #####=== CMake ===##### CMakeCache.txt diff --git a/.travis.yml b/.travis.yml index 952e56c2..ac3512bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,73 +1,138 @@ language: cpp sudo: required -dist: trusty +dist: xenial notifications: webhooks: urls: - - https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MHJlZF9za3klM0FvY2Vhbi5qb2Vkb25vZnJ5LmNvbS8lMjFldkFxa1BIWnVQSElHZWVuaGklM0FvY2Vhbi5qb2Vkb25vZnJ5LmNvbQ + - https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MHJlZF9za3klM0FuaGVrby5pbS8lMjFVYkNtSWxHVEhOSWdJUlpjcHQlM0FuaGVrby5pbQ on_success: always on_failure: always on_start: never email: false +cache: + directories: + - .hunter + - build-flatpak/.flatpak-builder + matrix: include: - os: osx compiler: clang - osx_image: xcode9 + # C++17 support + osx_image: xcode10.2 env: - - DEPLOYMENT=1 - - USE_BUNDLED_BOOST=0 - - USE_BUNDLED_CMARK=0 - - USE_BUNDLED_JSON=0 + - DEPLOYMENT=1 + addons: + homebrew: + taps: nlohmann/json + packages: + - clang-format + - cmake + - ninja + - openssl + - qt5 + update: true # workaround for broken travis homebrew - os: linux - compiler: gcc + compiler: gcc-7 env: - - CXX_COMPILER=g++-5 - - C_COMPILER=gcc-5 - - QT_VERSION="-5.10.1" - - QT_PKG=510 + - CXX=g++-7 + - CC=gcc-7 + - QT_PKG=512 - DEPLOYMENT=1 - - USE_BUNDLED_BOOST=1 - - USE_BUNDLED_CMARK=1 - - USE_BUNDLED_JSON=1 addons: apt: - sources: ["ubuntu-toolchain-r-test"] - packages: ["g++-5", "ninja-build"] + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt-5.12.6-xenial' + packages: + - g++-7 + - ninja-build + - qt512base + - qt512tools + - qt512svg + - qt512multimedia + - qt512quickcontrols2 + - qt512graphicaleffects + - liblmdb-dev + - libgl1-mesa-dev # needed for missing gl.h - os: linux - compiler: gcc + compiler: gcc-8 env: - - CXX_COMPILER=g++-8 - - C_COMPILER=gcc-8 - - QT_VERSION=571 - - QT_PKG=57 - - USE_BUNDLED_BOOST=1 - - USE_BUNDLED_CMARK=1 - - USE_BUNDLED_JSON=1 - addons: - apt: - sources: ["ubuntu-toolchain-r-test"] - packages: ["g++-8", "ninja-build"] - - os: linux - compiler: clang - env: - - CXX_COMPILER=clang++-5.0 - - C_COMPILER=clang-5.0 - - QT_VERSION=592 + - CXX=g++-8 + - CC=gcc-8 - QT_PKG=59 - - USE_BUNDLED_BOOST=1 - - USE_BUNDLED_CMARK=1 - - USE_BUNDLED_JSON=1 addons: apt: - sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-5.0"] - packages: ["clang-5.0", "g++-7", "ninja-build"] + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt597-xenial' + packages: + - g++-8 + - ninja-build + - qt59base + - qt59tools + - qt59svg + - qt59multimedia + - qt59quickcontrols2 + - qt59graphicaleffects + - liblmdb-dev + - libgl1-mesa-dev # needed for missing gl.h + - os: linux + compiler: clang-6 + env: + - CXX=clang++-6.0 + - CC=clang-6.0 + - QT_PKG=59 + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-6.0 + - sourceline: 'ppa:beineri/opt-qt597-xenial' + packages: + - clang++-6.0 + - g++-7 + - ninja-build + - qt59base + - qt59tools + - qt59svg + - qt59multimedia + - qt59quickcontrols2 + - qt59graphicaleffects + - liblmdb-dev + - libgl1-mesa-dev # needed for missing gl.h + - os: linux + env: + - DEPLOYMENT=1 + - FLATPAK=1 + - ARCH=amd64 + addons: + apt: + sources: + - sourceline: 'ppa:alexlarsson/flatpak' + packages: + - flatpak + - flatpak-builder + - elfutils + - os: linux + arch: arm64 + env: + - DEPLOYMENT=1 + - FLATPAK=1 + - ARCH=arm64 + addons: + apt: + sources: + - sourceline: 'ppa:alexlarsson/flatpak' + packages: + - flatpak + - flatpak-builder + - elfutils + - librsvg2-bin before_install: - - export CXX=${CXX_COMPILER} - - export CC=${C_COMPILER} # Use TRAVIS_TAG if defined, or the short commit SHA otherwise - export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)} install: @@ -79,6 +144,21 @@ script: - sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true - cp ./.ci/bintray-release.json . deploy: +- provider: s3 + access_key_id: $ARTIFACTS_KEY + secret_access_key: $ARTIFACTS_SECRET + bucket: $ARTIFACTS_BUCKET + region: $AWS_DEFAULT_REGION + detect_encoding: true + cache_control: "max-age=31536000" + skip_cleanup: true + acl: public_read + local_dir: artifacts + on: + condition: "$DEPLOYMENT == 1" + repo: Nheko-Reborn/nheko + tags: false + all_branches: true - provider: bintray user: "redsky17" key: diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f6c62f..55e59332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ ## [Unreleased] +### [0.7.0] -- Unreleased + +0.7.0 *requires* mtxclient 0.3.0. Make sure you compile against 0.3.0 +if you do not use the mtxclient bundled with nheko. + +#### Features +- Make nheko session import / export format match riot. Fixes #48 (WIP) +- Implement proper replies (WIP) +- Add .well-known support for auto-completing homeserver information +- Add mentions viewer so you can see all the messages you have been mentioned in (WIP) +- Add emoji font selection preference + +#### Improvements +- Add dedicated reply button to Timeline items. Add button for other options so + that right click isn't always required. +- Fix various things with regards to emoji rendering and the emoji picker (WIP) +- Lots and lots and lots of localization updates. +- Additional tweaks to the system theme + ## [0.6.4] - 2019-05-22 *Most* of the below fixes are due to updates in mtxclient. Make sure you compile against 0.2.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index fda60b71..146f2bb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,22 +1,86 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.13) option(APPVEYOR_BUILD "Build on appveyor" OFF) +option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF) option(ASAN "Compile with address sanitizers" OFF) +option(QML_DEBUGGING "Enable qml debugging" OFF) -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +set( + CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake" + CACHE + FILEPATH "Default toolchain" + ) -add_definitions(-DBOOST_MPL_LIMIT_LIST_SIZE=30) -add_definitions(-DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS) + +option(HUNTER_ENABLED "Enable Hunter package manager" OFF) +include("cmake/HunterGate.cmake") +HunterGate( + URL "https://github.com/cpp-pm/hunter/archive/v0.23.244.tar.gz" + SHA1 "2c0f491fd0b80f7b09e3d21adb97237161ef9835" + LOCAL + ) + +option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${HUNTER_ENABLED}) +option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_OLM "Use the bundled version of libolm." ${HUNTER_ENABLED}) +option(USE_BUNDLED_GTEST "Use the bundled version of Google Test." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_CMARK "Use the bundled version of cmark." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED}) +option(USE_BUNDLED_SODIUM "Use the bundled version of libsodium." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_ZLIB "Use the bundled version of zlib." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_LMDB "Use the bundled version of lmdb." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++." + ${HUNTER_ENABLED}) +option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny." + ${HUNTER_ENABLED}) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +if(${CMAKE_VERSION} VERSION_LESS "3.14.0") + message("Adding FetchContent_MakeAvailable") + # from cmakes sources + macro(FetchContent_MakeAvailable) + + foreach(contentName IN ITEMS ${ARGV}) + string(TOLOWER ${contentName} contentNameLower) + FetchContent_GetProperties(${contentName}) + if(NOT ${contentNameLower}_POPULATED) + FetchContent_Populate(${contentName}) + + # Only try to call add_subdirectory() if the populated content + # can be treated that way. Protecting the call with the check + # allows this function to be used for projects that just want + # to ensure the content exists, such as to provide content at + # a known location. + if(EXISTS ${${contentNameLower}_SOURCE_DIR}/CMakeLists.txt) + add_subdirectory(${${contentNameLower}_SOURCE_DIR} + ${${contentNameLower}_BINARY_DIR}) + endif() + endif() + endforeach() + + endmacro() +endif() include(GNUInstallDirs) # Include Qt basic functions include(QtCommon) -project(nheko LANGUAGES C CXX) +project(nheko LANGUAGES CXX C) set(CPACK_PACKAGE_VERSION_MAJOR "0") -set(CPACK_PACKAGE_VERSION_MINOR "6") -set(CPACK_PACKAGE_VERSION_PATCH "4") +set(CPACK_PACKAGE_VERSION_MINOR "7") +set(CPACK_PACKAGE_VERSION_PATCH "0") set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}) set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}) set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}) @@ -27,132 +91,125 @@ fix_project_version() # Set additional project information set(COMPANY "Nheko") -set(COPYRIGHT "Copyright (c) 2018 Nheko Contributors") +set(COPYRIGHT "Copyright (c) 2019 Nheko Contributors") set(IDENTIFIER "com.github.mujx.nheko") add_project_meta(META_FILES_TO_INCLUDE) -if(APPLE) - set(OPENSSL_ROOT_DIR /usr/local/opt/openssl) -endif() - if(NOT MSVC AND NOT APPLE) - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads REQUIRED) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) endif() if (BUILD_DOCS) - find_package(Doxygen) + find_package(Doxygen) - if (DOXYGEN_FOUND) - set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Doxyfile.in) - set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + if (DOXYGEN_FOUND) + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Doxyfile.in) + set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT}) + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT}) - add_custom_target(docs ALL - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" - VERBATIM ) - else (DOXYGEN_FOUND) - message("Doxygen need to be installed to generate the doxygen documentation") - endif (DOXYGEN_FOUND) + add_custom_target(docs ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM ) + else (DOXYGEN_FOUND) + message("Doxygen need to be installed to generate the doxygen documentation") + endif (DOXYGEN_FOUND) endif() # # LMDB # -include(LMDB) +#include(LMDB) +if(USE_BUNDLED_LMDB) + hunter_add_package(lmdb) + find_package(liblmdb CONFIG REQUIRED) +else() + find_package(LMDB) +endif() # # Discover Qt dependencies. # -find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia REQUIRED) +find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 QuickWidgets REQUIRED) +find_package(Qt5QuickCompiler) find_package(Qt5DBus) if (APPLE) - find_package(Qt5MacExtras REQUIRED) + find_package(Qt5MacExtras REQUIRED) endif(APPLE) if (Qt5Widgets_FOUND) - if (Qt5Widgets_VERSION VERSION_LESS 5.7.0) - message(STATUS "Qt version ${Qt5Widgets_VERSION}") - message(WARNING "Minimum supported Qt5 version is 5.7!") - endif() + if (Qt5Widgets_VERSION VERSION_LESS 5.9.0) + message(STATUS "Qt version ${Qt5Widgets_VERSION}") + message(WARNING "Minimum supported Qt5 version is 5.9!") + endif() endif(Qt5Widgets_FOUND) -# -# Set up compiler flags. -# -if (NOT MSVC) - set(CMAKE_C_COMPILER gcc) -endif(NOT MSVC) - -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) if(NOT MSVC) - set( - CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} \ - -Wall \ - -Wextra \ - -Werror \ - -pipe \ - -pedantic \ - -fsized-deallocation \ - -fdiagnostics-color=always \ - -Wunreachable-code \ - -std=c++14" - ) - if (NOT CMAKE_COMPILER_IS_GNUCXX) - # -Wshadow is buggy and broken in GCC, so do not enable it. - # see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79328 - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") - endif() + set( + CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} \ + -Wall \ + -Wextra \ + -pipe \ + -pedantic \ + -fsized-deallocation \ + -fdiagnostics-color=always \ + -Wunreachable-code \ + -std=c++17" + ) + if (NOT CMAKE_COMPILER_IS_GNUCXX) + # -Wshadow is buggy and broken in GCC, so do not enable it. + # see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79328 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") + endif() endif() if (MSVC) - set( - CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} /bigobj" - ) + set( + CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} /bigobj" + ) endif() if(NOT (CMAKE_BUILD_TYPE OR CMAKE_CONFIGURATION_TYPES)) - set(CMAKE_BUILD_TYPE "Debug" CACHE STRING - "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." - FORCE) - message("Setting build type to '${CMAKE_BUILD_TYPE}'") + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) + message("Setting build type to '${CMAKE_BUILD_TYPE}'") else(NOT (CMAKE_BUILD_TYPE OR CMAKE_CONFIGURATION_TYPES)) - message("Build type set to '${CMAKE_BUILD_TYPE}'") + message("Build type set to '${CMAKE_BUILD_TYPE}'") endif(NOT (CMAKE_BUILD_TYPE OR CMAKE_CONFIGURATION_TYPES)) set(SPDLOG_DEBUG_ON false) # Windows doesn't handle CMAKE_BUILD_TYPE. if(NOT WIN32) - if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set(SPDLOG_DEBUG_ON true) - else() - set(SPDLOG_DEBUG_ON false) - endif() + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(SPDLOG_DEBUG_ON true) + else() + set(SPDLOG_DEBUG_ON false) + endif() endif() find_program(GIT git) if(GIT) - execute_process( - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMAND ${GIT} rev-parse --short HEAD - OUTPUT_VARIABLE GIT_OUT OUTPUT_STRIP_TRAILING_WHITESPACE - ) - if(GIT_OUT) - set(CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH}-${GIT_OUT}") - else() - set(CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH}") - endif() + execute_process( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${GIT} rev-parse --short HEAD + OUTPUT_VARIABLE GIT_OUT OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(GIT_OUT) + set(CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH}-${GIT_OUT}") + else() + set(CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH}") + endif() endif(GIT) set(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}) @@ -169,221 +226,281 @@ configure_file(cmake/nheko.h config/nheko.h) # Declare source and header files. # set(SRC_FILES - # Dialogs - src/dialogs/CreateRoom.cpp - src/dialogs/ImageOverlay.cpp - src/dialogs/PreviewUploadOverlay.cpp - src/dialogs/InviteUsers.cpp - src/dialogs/JoinRoom.cpp - src/dialogs/MemberList.cpp - src/dialogs/LeaveRoom.cpp - src/dialogs/Logout.cpp - src/dialogs/UserProfile.cpp - src/dialogs/ReadReceipts.cpp - src/dialogs/ReCaptcha.cpp - src/dialogs/RoomSettings.cpp + # Dialogs + src/dialogs/CreateRoom.cpp + src/dialogs/FallbackAuth.cpp + src/dialogs/ImageOverlay.cpp + src/dialogs/InviteUsers.cpp + src/dialogs/JoinRoom.cpp + src/dialogs/LeaveRoom.cpp + src/dialogs/Logout.cpp + src/dialogs/MemberList.cpp + src/dialogs/PreviewUploadOverlay.cpp + src/dialogs/ReCaptcha.cpp + src/dialogs/ReadReceipts.cpp + src/dialogs/RoomSettings.cpp + src/dialogs/UserProfile.cpp - # Emoji - src/emoji/Category.cpp - src/emoji/ItemDelegate.cpp - src/emoji/Panel.cpp - src/emoji/PickButton.cpp - src/emoji/Provider.cpp + # Emoji + src/emoji/Category.cpp + src/emoji/ItemDelegate.cpp + src/emoji/Panel.cpp + src/emoji/PickButton.cpp + src/emoji/Provider.cpp - # Timeline - src/timeline/TimelineViewManager.cpp - src/timeline/TimelineItem.cpp - src/timeline/TimelineView.cpp - src/timeline/widgets/AudioItem.cpp - src/timeline/widgets/FileItem.cpp - src/timeline/widgets/ImageItem.cpp - src/timeline/widgets/VideoItem.cpp + # Timeline + src/timeline/TimelineViewManager.cpp + src/timeline/TimelineModel.cpp + src/timeline/DelegateChooser.cpp - # UI components - src/ui/Avatar.cpp - src/ui/Badge.cpp - src/ui/LoadingIndicator.cpp - src/ui/InfoMessage.cpp - src/ui/FlatButton.cpp - src/ui/FloatingButton.cpp - src/ui/Label.cpp - src/ui/OverlayModal.cpp - src/ui/SnackBar.cpp - src/ui/RaisedButton.cpp - src/ui/Ripple.cpp - src/ui/RippleOverlay.cpp - src/ui/OverlayWidget.cpp - src/ui/TextField.cpp - src/ui/TextLabel.cpp - src/ui/ToggleButton.cpp - src/ui/Theme.cpp - src/ui/ThemeManager.cpp + # UI components + src/ui/Avatar.cpp + src/ui/Badge.cpp + src/ui/DropShadow.cpp + src/ui/LoadingIndicator.cpp + src/ui/InfoMessage.cpp + src/ui/FlatButton.cpp + src/ui/FloatingButton.cpp + src/ui/Label.cpp + src/ui/OverlayModal.cpp + src/ui/SnackBar.cpp + src/ui/RaisedButton.cpp + src/ui/Ripple.cpp + src/ui/RippleOverlay.cpp + src/ui/OverlayWidget.cpp + src/ui/TextField.cpp + src/ui/TextLabel.cpp + src/ui/ToggleButton.cpp + src/ui/Theme.cpp + src/ui/ThemeManager.cpp - src/AvatarProvider.cpp - src/Cache.cpp - src/ChatPage.cpp - src/CommunitiesListItem.cpp - src/CommunitiesList.cpp - src/InviteeItem.cpp - src/LoginPage.cpp - src/Logging.cpp - src/MainWindow.cpp - src/MatrixClient.cpp - src/QuickSwitcher.cpp - src/Olm.cpp - src/RegisterPage.cpp - src/RoomInfoListItem.cpp - src/RoomList.cpp - src/RunGuard.cpp - src/SideBarActions.cpp - src/Splitter.cpp - src/SuggestionsPopup.cpp - src/TextInputWidget.cpp - src/TopRoomBar.cpp - src/TrayIcon.cpp - src/TypingDisplay.cpp - src/Utils.cpp - src/UserInfoWidget.cpp - src/UserSettingsPage.cpp - src/WelcomePage.cpp - src/main.cpp -) + src/AvatarProvider.cpp + src/Cache.cpp + src/ChatPage.cpp + src/CommunitiesListItem.cpp + src/CommunitiesList.cpp + src/EventAccessors.cpp + src/InviteeItem.cpp + src/LoginPage.cpp + src/Logging.cpp + src/MainWindow.cpp + src/MatrixClient.cpp + src/MxcImageProvider.cpp + src/ColorImageProvider.cpp + src/QuickSwitcher.cpp + src/Olm.cpp + src/RegisterPage.cpp + src/RoomInfoListItem.cpp + src/RoomList.cpp + src/SideBarActions.cpp + src/Splitter.cpp + src/popups/SuggestionsPopup.cpp + src/popups/PopupItem.cpp + src/popups/ReplyPopup.cpp + src/popups/UserMentions.cpp + src/TextInputWidget.cpp + src/TopRoomBar.cpp + src/TrayIcon.cpp + src/Utils.cpp + src/UserInfoWidget.cpp + src/UserSettingsPage.cpp + src/WelcomePage.cpp + src/main.cpp + ) -# ExternalProject dependencies -set(EXTERNAL_PROJECT_DEPS "") include(FeatureSummary) -set(Boost_USE_STATIC_LIBS OFF) -set(Boost_USE_STATIC_RUNTIME OFF) -set(Boost_USE_MULTITHREADED ON) -find_package(Boost 1.66 REQUIRED - COMPONENTS atomic - chrono - date_time - iostreams - random - regex - system - thread) +if(USE_BUNDLED_BOOST) + hunter_add_package(Boost COMPONENTS iostreams system thread) +endif() +find_package(Boost 1.70 REQUIRED + COMPONENTS iostreams + system + thread) +if(USE_BUNDLED_ZLIB) + hunter_add_package(ZLIB) +endif() find_package(ZLIB REQUIRED) +if(USE_BUNDLED_OPENSSL) + hunter_add_package(OpenSSL) +endif() find_package(OpenSSL REQUIRED) -find_package(MatrixClient 0.1.0 REQUIRED) -find_package(Olm 2 REQUIRED) +if(USE_BUNDLED_MTXCLIENT) + include(FetchContent) + set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") + set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") + FetchContent_Declare( + MatrixClient + GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git + GIT_TAG 7fc1d357afaabb134cb6d9c593f94915973d31fa + ) + FetchContent_MakeAvailable(MatrixClient) +else() + find_package(MatrixClient 0.3.0 REQUIRED) +endif() +if(USE_BUNDLED_OLM) + include(FetchContent) + set(OLM_TESTS OFF CACHE INTERNAL "") + FetchContent_Declare( + Olm + GIT_REPOSITORY https://gitlab.matrix.org/matrix-org/olm.git + GIT_TAG 3.1.4 + ) + FetchContent_MakeAvailable(Olm) +else() + find_package(Olm 3) + set_package_properties(Olm PROPERTIES + DESCRIPTION "An implementation of the Double Ratchet cryptographic ratchet" + URL "https://git.matrix.org/git/olm/about/" + TYPE REQUIRED + ) +endif() +if(USE_BUNDLED_SPDLOG) + hunter_add_package(spdlog) +endif() find_package(spdlog 1.0.0 CONFIG REQUIRED) -find_package(cmark REQUIRED) + +if(USE_BUNDLED_CMARK) + include(FetchContent) + FetchContent_Declare( + cmark + GIT_REPOSITORY https://github.com/commonmark/cmark.git + GIT_TAG 242e277a661ec7e51f34dcaf86c1925d550b1498 #0.29.0 << doesn't work with fetch content yet + CMAKE_ARGS "CMARK_STATIC=ON CMARK_SHARED=OFF CMARK_TESTS=OFF CMARK_TESTS=OFF" + ) + FetchContent_MakeAvailable(cmark) + if (MSVC) + add_library(cmark::cmark ALIAS libcmark) + else() + add_library(cmark::cmark ALIAS libcmark_static) + endif() +else() + find_package(cmark REQUIRED) +endif() + +if(USE_BUNDLED_JSON) + hunter_add_package(nlohmann_json) +endif() find_package(nlohmann_json 3.2.0) set_package_properties(nlohmann_json PROPERTIES - DESCRIPTION "JSON for Modern C++, a C++11 header-only JSON class" - URL "https://nlohmann.github.io/json/" - TYPE REQUIRED -) + DESCRIPTION "JSON for Modern C++, a C++11 header-only JSON class" + URL "https://nlohmann.github.io/json/" + TYPE REQUIRED + ) -if(NOT LMDBXX_INCLUDE_DIR) - find_path(LMDBXX_INCLUDE_DIR - NAMES lmdb++.h - PATHS /usr/include - /usr/local/include - $ENV{LIB_DIR}/include - $ENV{LIB_DIR}/include/lmdbxx) +if(USE_BUNDLED_LMDBXX) + hunter_add_package(lmdbxx) + find_package(lmdbxx CONFIG REQUIRED) +else() + if(NOT LMDBXX_INCLUDE_DIR) + find_path(LMDBXX_INCLUDE_DIR + NAMES lmdb++.h + PATHS /usr/include + /usr/local/include + $ENV{LIB_DIR}/include + $ENV{LIB_DIR}/include/lmdbxx) + + endif() + add_library(lmdbxx INTERFACE) + target_include_directories(lmdbxx INTERFACE ${LMDBXX_INCLUDE_DIR}) + add_library(lmdbxx::lmdbxx ALIAS lmdbxx) endif() -include_directories(SYSTEM ${LMDBXX_INCLUDE_DIR}) -if(NOT TWEENY_INCLUDE_DIR) - find_path(TWEENY_INCLUDE_DIR - NAMES tweeny/tweeny.h - PATHS /usr/include/ - /usr/local/include/ - $ENV{LIB_DIR}/include/ - $ENV{LIB_DIR}/include/tweeny) +if(USE_BUNDLED_TWEENY) + include(FetchContent) + FetchContent_Declare( + Tweeny + GIT_REPOSITORY https://github.com/mobius3/tweeny.git + GIT_TAG 6a5033372fe53c4c731c66c8a2d56261746cd85c #v3 <- v3 has unfixed warnings + ) + FetchContent_MakeAvailable(Tweeny) +else() + find_package(Tweeny REQUIRED) endif() -include_directories(SYSTEM ${TWEENY_INCLUDE_DIR}) -include_directories(${CMAKE_SOURCE_DIR}/src) -include_directories(${Boost_INCLUDE_DIRS}) - -# local inclue directory -include_directories(includes) +# single instance functionality +set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") +add_subdirectory(third_party/SingleApplication-3.0.19/) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) qt5_wrap_cpp(MOC_HEADERS - # Dialogs - src/dialogs/CreateRoom.h - src/dialogs/ImageOverlay.h - src/dialogs/PreviewUploadOverlay.h - src/dialogs/InviteUsers.h - src/dialogs/JoinRoom.h - src/dialogs/MemberList.h - src/dialogs/LeaveRoom.h - src/dialogs/Logout.h - src/dialogs/UserProfile.h - src/dialogs/RawMessage.h - src/dialogs/ReadReceipts.h - src/dialogs/ReCaptcha.h - src/dialogs/RoomSettings.h + # Dialogs + src/dialogs/CreateRoom.h + src/dialogs/FallbackAuth.h + src/dialogs/ImageOverlay.h + src/dialogs/InviteUsers.h + src/dialogs/JoinRoom.h + src/dialogs/LeaveRoom.h + src/dialogs/Logout.h + src/dialogs/MemberList.h + src/dialogs/PreviewUploadOverlay.h + src/dialogs/RawMessage.h + src/dialogs/ReCaptcha.h + src/dialogs/ReadReceipts.h + src/dialogs/RoomSettings.h + src/dialogs/UserProfile.h - # Emoji - src/emoji/Category.h - src/emoji/ItemDelegate.h - src/emoji/Panel.h - src/emoji/PickButton.h + # Emoji + src/emoji/Category.h + src/emoji/ItemDelegate.h + src/emoji/Panel.h + src/emoji/PickButton.h - # Timeline - src/timeline/TimelineItem.h - src/timeline/TimelineView.h - src/timeline/TimelineViewManager.h - src/timeline/widgets/AudioItem.h - src/timeline/widgets/FileItem.h - src/timeline/widgets/ImageItem.h - src/timeline/widgets/VideoItem.h + # Timeline + src/timeline/TimelineViewManager.h + src/timeline/TimelineModel.h + src/timeline/DelegateChooser.h - # UI components - src/ui/Avatar.h - src/ui/Badge.h - src/ui/LoadingIndicator.h - src/ui/InfoMessage.h - src/ui/FlatButton.h - src/ui/Label.h - src/ui/FloatingButton.h - src/ui/Menu.h - src/ui/OverlayWidget.h - src/ui/SnackBar.h - src/ui/RaisedButton.h - src/ui/Ripple.h - src/ui/RippleOverlay.h - src/ui/TextField.h - src/ui/TextLabel.h - src/ui/ToggleButton.h - src/ui/Theme.h - src/ui/ThemeManager.h + # UI components + src/ui/Avatar.h + src/ui/Badge.h + src/ui/LoadingIndicator.h + src/ui/InfoMessage.h + src/ui/FlatButton.h + src/ui/Label.h + src/ui/FloatingButton.h + src/ui/Menu.h + src/ui/OverlayWidget.h + src/ui/SnackBar.h + src/ui/RaisedButton.h + src/ui/Ripple.h + src/ui/RippleOverlay.h + src/ui/TextField.h + src/ui/TextLabel.h + src/ui/ToggleButton.h + src/ui/Theme.h + src/ui/ThemeManager.h - src/notifications/Manager.h + src/notifications/Manager.h - src/AvatarProvider.h - src/Cache.h - src/ChatPage.h - src/CommunitiesListItem.h - src/CommunitiesList.h - src/LoginPage.h - src/MainWindow.h - src/MatrixClient.h - src/InviteeItem.h - src/QuickSwitcher.h - src/RegisterPage.h - src/RoomInfoListItem.h - src/RoomList.h - src/SideBarActions.h - src/Splitter.h - src/SuggestionsPopup.h - src/TextInputWidget.h - src/TopRoomBar.h - src/TrayIcon.h - src/TypingDisplay.h - src/UserInfoWidget.h - src/UserSettingsPage.h - src/WelcomePage.h -) + src/AvatarProvider.h + src/Cache_p.h + src/ChatPage.h + src/CommunitiesListItem.h + src/CommunitiesList.h + src/LoginPage.h + src/MainWindow.h + src/MxcImageProvider.h + src/InviteeItem.h + src/QuickSwitcher.h + src/RegisterPage.h + src/RoomInfoListItem.h + src/RoomList.h + src/SideBarActions.h + src/Splitter.h + src/popups/SuggestionsPopup.h + src/popups/ReplyPopup.h + src/popups/PopupItem.h + src/popups/UserMentions.h + src/TextInputWidget.h + src/TopRoomBar.h + src/TrayIcon.h + src/UserInfoWidget.h + src/UserSettingsPage.h + src/WelcomePage.h + ) # # Bundle translations. @@ -391,85 +508,104 @@ qt5_wrap_cpp(MOC_HEADERS include(Translations) set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC}) -set(COMMON_LIBS - MatrixClient::MatrixClient - ${Boost_LIBRARIES} - cmark::cmark - Qt5::Widgets - Qt5::Svg - Qt5::Concurrent - Qt5::Multimedia - nlohmann_json::nlohmann_json) - -if(APPVEYOR_BUILD) - set(NHEKO_LIBS ${COMMON_LIBS} lmdb) -else() - set(NHEKO_LIBS ${COMMON_LIBS} ${LMDB_LIBRARY}) -endif() - if (APPLE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") - set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") + set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm) elseif (WIN32) - file(DOWNLOAD - "https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp" - ${PROJECT_SOURCE_DIR}/src/wintoastlib.cpp - EXPECTED_HASH SHA256=1A1A7CE41C1052B12946798F4A6C67CE1FAD209C967F5ED4D720B173527E2073) + file(DOWNLOAD + "https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp" + ${PROJECT_SOURCE_DIR}/src/wintoastlib.cpp + EXPECTED_HASH SHA256=1A1A7CE41C1052B12946798F4A6C67CE1FAD209C967F5ED4D720B173527E2073) - file(DOWNLOAD - "https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.h" - ${PROJECT_SOURCE_DIR}/src/wintoastlib.h - EXPECTED_HASH SHA256=b4481023c5782733795838be22bf1a75f45d87458cd4d9a5a75f664a146eea11) + file(DOWNLOAD + "https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.h" + ${PROJECT_SOURCE_DIR}/src/wintoastlib.h + EXPECTED_HASH SHA256=b4481023c5782733795838be22bf1a75f45d87458cd4d9a5a75f664a146eea11) - set(SRC_FILES ${SRC_FILES} src/notifications/ManagerWin.cpp src/wintoastlib.cpp) + set(SRC_FILES ${SRC_FILES} src/notifications/ManagerWin.cpp src/wintoastlib.cpp) else () - set(SRC_FILES ${SRC_FILES} src/notifications/ManagerLinux.cpp) + set(SRC_FILES ${SRC_FILES} src/notifications/ManagerLinux.cpp) endif () set(NHEKO_DEPS - ${SRC_FILES} - ${UI_HEADERS} - ${MOC_HEADERS} - ${TRANSLATION_DEPS} - ${META_FILES_TO_INCLUDE}) + ${SRC_FILES} + ${UI_HEADERS} + ${MOC_HEADERS} + ${TRANSLATION_DEPS} + ${META_FILES_TO_INCLUDE}) if(ASAN) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined") endif() +add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) if(APPLE) - add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) - target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras) + target_link_libraries (nheko PRIVATE Qt5::MacExtras) elseif(WIN32) - add_executable (nheko ${OS_BUNDLE} ${ICON_FILE} ${NHEKO_DEPS}) - target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain) + target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN) + target_link_libraries (nheko PRIVATE ${NTDLIB} Qt5::WinMain) else() - add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) - target_link_libraries (nheko ${NHEKO_LIBS} Qt5::DBus) + target_link_libraries (nheko PRIVATE Qt5::DBus) +endif() +target_include_directories(nheko PRIVATE src includes) + +target_link_libraries(nheko PRIVATE + MatrixClient::MatrixClient + Boost::iostreams + Boost::system + Boost::thread + cmark::cmark + spdlog::spdlog + Qt5::Widgets + Qt5::Svg + Qt5::Concurrent + Qt5::Multimedia + Qt5::Qml + Qt5::QuickControls2 + Qt5::QuickWidgets + nlohmann_json::nlohmann_json + lmdbxx::lmdbxx + liblmdb::lmdb + tweeny + SingleApplication::SingleApplication) + +if(MSVC) + target_link_libraries(nheko PRIVATE ntdll) endif() -if(EXTERNAL_PROJECT_DEPS) - add_dependencies(nheko ${EXTERNAL_PROJECT_DEPS}) + +if(QML_DEBUGGING) + target_compile_definitions(nheko PRIVATE QML_DEBUGGING) endif() + +if(NOT MSVC) + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR CI_BUILD) + target_compile_options(nheko PRIVATE "-Werror") + endif() +endif() + +set_target_properties(nheko PROPERTIES SKIP_BUILD_RPATH TRUE) + if(UNIX AND NOT APPLE) - install (TARGETS nheko RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") - install (FILES "resources/nheko-16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "nheko.png") - install (FILES "resources/nheko-32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "nheko.png") - install (FILES "resources/nheko-48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "nheko.png") - install (FILES "resources/nheko-64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "nheko.png") - install (FILES "resources/nheko-128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "nheko.png") - install (FILES "resources/nheko-256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "nheko.png") - install (FILES "resources/nheko-512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "nheko.png") - install (FILES "resources/nheko.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") - install (FILES "resources/nheko.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") + install (TARGETS nheko RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + install (FILES "resources/nheko-16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "nheko.png") + install (FILES "resources/nheko-32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "nheko.png") + install (FILES "resources/nheko-48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "nheko.png") + install (FILES "resources/nheko-64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "nheko.png") + install (FILES "resources/nheko-128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "nheko.png") + install (FILES "resources/nheko-256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "nheko.png") + install (FILES "resources/nheko-512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "nheko.png") + install (FILES "resources/nheko.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps" RENAME "nheko.svg") + install (FILES "resources/nheko.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") + install (FILES "resources/nheko.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") - if(NOT TARGET uninstall) - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY) - add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) - endif() + if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + endif() endif() diff --git a/Dockerfile b/Dockerfile index 2e01b40b..dddd1c6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN \ add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ apt-get update -qq && \ apt-get install -y \ - qt510base qt510tools qt510svg qt510multimedia \ + qt510base qt510tools qt510svg qt510multimedia qt510quickcontrols2 qt510graphicaleffects \ gcc-5 g++-5 RUN \ @@ -44,4 +44,4 @@ ENV PATH=/opt/qt510/bin:$PATH RUN mkdir /build -WORKDIR /build \ No newline at end of file +WORKDIR /build diff --git a/Makefile b/Makefile index 2f688d3b..7f603dcb 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ update-translations: -locations relative \ -Iinclude/dialogs \ -Iinclude \ - src/ -ts resources/langs/nheko_*.ts -no-obsolete + src/ resources/qml/ -ts resources/langs/nheko_*.ts -no-obsolete clean: rm -rf build diff --git a/README.md b/README.md index ed0cb0fb..db522f70 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ nheko [![Build Status](https://travis-ci.org/Nheko-Reborn/nheko.svg?branch=master)](https://travis-ci.org/Nheko-Reborn/nheko) [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master) [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.6.4) -[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://bintray.com/nheko-reborn/nheko/nheko) +[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://nheko-reborn-artifacts.s3.us-east-2.amazonaws.com/list.html) [![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org) [![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko) Download on Flathub @@ -11,21 +11,28 @@ nheko The motivation behind the project is to provide a native desktop app for [Matrix] that feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IRC client. +### Translations ### +[![Translation status](http://weblate.nheko.im/widgets/nheko/-/nheko-master/svg-badge.svg)](http://weblate.nheko.im/engage/nheko/?utm_source=widget) + +Help us with translations so as many people as possible will be able to use nheko! + ### Note regarding End-to-End encryption Currently the implementation is at best a **proof of concept** and it should only be used for -testing purposes. +testing purposes. Most importantly, it is missing device verification, so while your messages +and media are encrypted, nheko doesn't verify who gets the messages. ## Features Most of the features you would expect from a chat application are missing right now but we are getting close to a more feature complete client. Specifically there is support for: -- E2E encryption (text messages only: attachments are currently sent unencrypted). +- E2E encryption. - User registration. - Creating, joining & leaving rooms. - Sending & receiving invites. - Sending & receiving files and emoji (inline widgets for images, audio and file messages). +- Replies with text, images and other media (and actually render them as inline widgets). - Typing notifications. - Username auto-completion. - Message & mention notifications. @@ -62,7 +69,7 @@ sudo dnf install nheko #### Gentoo Linux ```bash -sudo layman -a matrix +sudo eselect repository enable matrix sudo emerge -a nheko ``` @@ -86,7 +93,8 @@ flatpak install flathub io.github.NhekoReborn.Nheko guix install nheko ``` -#### macOS (10.12 and above) +#### macOS (10.14 and above) + with [macports](https://www.macports.org/) : @@ -96,21 +104,43 @@ sudo port install nheko ### Build Requirements -- Qt5 (5.7 or greater). Qt 5.7 adds support for color font rendering with - Freetype, which is essential to properly support emoji. -- CMake 3.1 or greater. +- Qt5 (5.10 or greater). Qt 5.7 adds support for color font rendering with + Freetype, which is essential to properly support emoji, 5.8 adds some features + to make interopability with Qml easier, 5.10 makes sliders actually visible with different palettes. +- CMake 3.15 or greater. (Lower version may work, but may break boost linking) - [mtxclient](https://github.com/Nheko-Reborn/mtxclient) - [LMDB](https://symas.com/lightning-memory-mapped-database/) -- [cmark](https://github.com/commonmark/cmark) -- Boost 1.66 or greater. +- [cmark](https://github.com/commonmark/cmark) 0.29 or greater. +- Boost 1.70 or greater. - [libolm](https://git.matrix.org/git/olm) - [libsodium](https://github.com/jedisct1/libsodium) - [spdlog](https://github.com/gabime/spdlog) -- A compiler that supports C++ 14: - - Clang 5 (tested on Travis CI) +- A compiler that supports C++ 17: + - Clang 6 (tested on Travis CI) - GCC 7 (tested on Travis CI) - MSVC 19.13 (tested on AppVeyor) +Nheko can use bundled version for most of those libraries automatically, if the versions in your distro are too old. +To use them, you can enable the hunter integration by passing `-DHUNTER_ENABLED=ON`. +It is probably wise to link those dependencies statically by passing `-DBUILD_SHARED_LIBS=OFF` +You can select which bundled dependencies you want to use py passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter. + +The bundle flags are currently: + +- USE_BUNDLED_BOOST +- USE_BUNDLED_SPDLOG +- USE_BUNDLED_OLM +- USE_BUNDLED_GTEST +- USE_BUNDLED_CMARK +- USE_BUNDLED_JSON +- USE_BUNDLED_OPENSSL +- USE_BUNDLED_MTXCLIENT +- USE_BUNDLED_SODIUM +- USE_BUNDLED_ZLIB +- USE_BUNDLED_LMDB +- USE_BUNDLED_LMDBXX +- USE_BUNDLED_TWEENY + #### Linux If you don't want to install any external dependencies, you can generate an AppImage locally using docker. @@ -138,26 +168,45 @@ sudo pacman -S qt5-base \ ##### Gentoo Linux ```bash -sudo emerge -a ">=dev-qt/qtgui-5.7.1" media-libs/fontconfig +sudo emerge -a ">=dev-qt/qtgui-5.9.0" media-libs/fontconfig ``` -##### Ubuntu (e.g 14.04) +##### Ubuntu 16.04 ```bash -sudo add-apt-repository ppa:beineri/opt-qt592-trusty +sudo add-apt-repository ppa:beineri/opt-qt592-xenial sudo add-apt-repository ppa:george-edison55/cmake-3.x sudo add-apt-repository ppa:ubuntu-toolchain-r-test sudo apt-get update sudo apt-get install -y g++-7 qt59base qt59svg qt59tools qt59multimedia cmake liblmdb-dev libsodium-dev ``` +##### Ubuntu 19.10 + +```bash +# Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports): +sudo apt install g++-7 cmake liblmdb-dev libsodium-dev qt{base,tools,multimedia}5-dev qml-module-qt{gstreamer,multimedia,quick-extras} libqt5svg5-dev qt{script,quickcontrols2-}5-dev +``` + +##### Debian Buster (or higher probably) + +(User report, not sure if all of those are needed) + +```bash +sudo apt install cmake gcc make automake liblmdb-dev libsodium-dev \ + qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \ + qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools \ + qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts +``` + ##### Guix ```bash guix environment nheko ``` -##### macOS (Xcode 8 or later) +##### macOS (Xcode 10.2 or later) + ```bash brew update @@ -170,61 +219,29 @@ brew install qt5 lmdb cmake llvm libsodium spdlog boost cmark (for the CMake integration) workloads. 2. Download the latest Qt for windows installer and install it somewhere. -Make sure to install the `MSVC 2017 64-bit` toolset for at least Qt 5.9 +Make sure to install the `MSVC 2017 64-bit` toolset for at least Qt 5.10 (lower versions does not support VS2017). -3. Install dependencies with `vcpkg`. You can simply clone it into a subfolder -of the root nheko source directory. - -```powershell -git clone http:\\github.com\Microsoft\vcpkg -cd vcpkg -.\bootstrap-vcpkg.bat -.\vcpkg install --triplet x64-windows \ - boost-asio \ - boost-beast \ - boost-iostreams \ - boost-random \ - boost-signals2 \ - boost-system \ - boost-thread \ - cmark \ - libsodium \ - lmdb \ - openssl \ - zlib -``` - -4. Install dependencies not managed by vcpkg. (libolm, libmtxclient, libmatrix_structs) - -Inside the project root run the following (replacing the path to vcpkg as necessary). - -```bash -cmake -G "Visual Studio 15 2017 Win64" -Hdeps -B.deps - -DCMAKE_TOOLCHAIN_FILE=C:/Users//vcpkg/scripts/buildsystems/vcpkg.cmake - -DUSE_BUNDLED_BOOST=OFF -cmake --build .deps --config Release -cmake --build .deps --config Debug -``` +3. If you don't have openssl installed, you will need to install perl to build it (i.e. Strawberry Perl). ### Building -First we need to install the rest of the dependencies that are not available in our system +We can now build nheko: ```bash -cmake -Hdeps -B.deps \ - -DUSE_BUNDLED_BOOST=OFF # if we already have boost & spdlog installed. - -DUSE_BUNDLED_SPDLOG=OFF -cmake --build .deps -``` - -We can now build nheko by pointing it to the path that we installed the dependencies. - -```bash -cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=.deps/usr +cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release cmake --build build ``` +To use bundled dependencies you can use hunter, i.e.: + +```bash +cmake -H. -Bbuild -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=OFF +cmake --build build --config Release +``` + +Adapt the USE_BUNDLED_* as needed. + If the build fails with the following error ``` Could not find a package configuration file provided by "Qt5Widgets" with @@ -249,13 +266,14 @@ The `nheko` binary will be located in the `build` directory. After installing all dependencies, you need to edit the `CMakeSettings.json` to be able to load and compile nheko within Visual Studio. -You need to fill out the paths for the `CMAKE_TOOLCHAIN_FILE` and the `Qt5_DIR`. -The toolchain file should point to the `vcpkg.cmake` and the Qt5 dir to the `lib\cmake\Qt5` dir. +You need to fill out the paths for the `Qt5_DIR`. +The Qt5 dir should point to the `lib\cmake\Qt5` dir. Examples for the paths are: - - `C:\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake` - `C:\\Qt\\5.10.1\\msvc2017_64\\lib\\cmake\\Qt5` +You should also enable hunter by setting `HUNTER_ENABLED` to `ON` and `BUILD_SHARED_LIBS` to `OFF`. + Now right click into the root nheko source directory and choose `Open in Visual Studio`. You can choose the build type Release and Debug in the top toolbar. After a successful CMake generation you can select the `nheko.exe` as the run target. @@ -273,6 +291,9 @@ windeployqt nheko.exe The final binary will be located inside `build-vc\Release\Release` for the Release build and `build-vc\Debug\Debug` for the Debug build. +Also copy the respective cmark.dll to the binary dir from `build/cmark-build/src/Release` (or Debug). + + ### Contributing See [CONTRIBUTING](.github/CONTRIBUTING.md) @@ -288,9 +309,7 @@ Here are some screen shots to get a feel for the UI, but things will probably ch ### Third party -- [Emoji One](http://emojione.com) -- [Font Awesome](http://fontawesome.io/) -- [Open Sans](https://fonts.google.com/specimen/Open+Sans) +[Single Application for Qt](https://github.com/itay-grudev/SingleApplication) [Matrix]:https://matrix.org [Riot]:https://riot.im diff --git a/appveyor.yml b/appveyor.yml index 08251174..78b57139 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ --- -version: 0.6.4-{build} +version: 0.7.0-{build} configuration: Release image: Visual Studio 2017 @@ -10,30 +10,17 @@ environment: BINTRAY_APIKEY: secure: "iGl5mzE9/ta9kFELUxDw9XtlYMSCMai9xowXIkYzU8WKHz7NfW0mLwMJZvblZFXJ" -cache: c:\tools\vcpkg\installed\ +cache: + - c:\hunter\ -> appveyor.yml + - build\_deps -> appveyor.yml,deps\CMakeLists.txt build: verbosity: minimal install: - - set QT_DIR=C:\Qt\5.10.1\msvc2017_64 - - set PATH=%PATH%;%QT_DIR%\bin;C:\MinGW\bin - - set PATH=%PATH%;C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin + - set QT_DIR=C:\Qt\5.13\msvc2017_64 + - set PATH=%PATH%;%QT_DIR%\bin - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - - cd "C:\Tools\vcpkg"&& git pull && .\bootstrap-vcpkg.bat && cd %APPVEYOR_BUILD_FOLDER% - - vcpkg install - nlohmann-json:%PLATFORM%-windows - boost-asio:%PLATFORM%-windows - boost-beast:%PLATFORM%-windows - boost-iostreams:%PLATFORM%-windows - boost-random:%PLATFORM%-windows - boost-signals2:%PLATFORM%-windows - boost-system:%PLATFORM%-windows - boost-thread:%PLATFORM%-windows - libsodium:%PLATFORM%-windows - lmdb:%PLATFORM%-windows - openssl:%PLATFORM%-windows - zlib:%PLATFORM%-windows build_script: # VERSION format: branch-master/branch-1.2 @@ -54,23 +41,13 @@ build_script: - echo %INSTVERSION% - echo %DATE% - # Build & install the dependencies - - cmake -G "Visual Studio 15 2017 Win64" -Hdeps -B.deps - -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake - -DUSE_BUNDLED_BOOST=OFF - -DUSE_BUNDLED_JSON=OFF - -DMTX_STATIC=ON - - cmake --build .deps --config Release - # Build nheko - - rm -f cmake/FindOlm.cmake + #- cmake -G "Visual Studio 16 2019" -A x64 -H. -Bbuild - cmake -G "Visual Studio 15 2017 Win64" -H. -Bbuild - -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake - -DLMDBXX_INCLUDE_DIR=.deps/usr/include - -DTWEENY_INCLUDE_DIR=.deps/usr/include - -DCMARK_INCLUDE_DIR=C:/projects/nheko/.deps/usr/include - -DCMARK_LIBRARY=C:/projects/nheko/.deps/usr/lib/cmark.lib - -DJSON_INCLUDE_DIR=.deps/usr/include + -DHUNTER_ROOT="C:\hunter" + -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF + -DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release + - cmake --build build --config Release after_build: @@ -79,14 +56,9 @@ after_build: - echo %BUILD% - mkdir NhekoRelease - copy build\Release\nheko.exe NhekoRelease\nheko.exe + - copy build\_deps\cmark-build\src\Release\cmark.dll NhekoRelease\cmark.dll - windeployqt --qmldir %QT_DIR%\qml\ --release NhekoRelease\nheko.exe - - copy C:\Tools\vcpkg\installed\x64-windows\lib\*.lib .\NhekoRelease\ - - copy C:\Tools\vcpkg\installed\x64-windows\bin\*.dll .\NhekoRelease\ - - - copy C:\projects\nheko\.deps\usr\lib\cmark.lib .\NhekoRelease\ - - copy C:\projects\nheko\.deps\usr\bin\cmark.dll .\NhekoRelease\ - - 7z a nheko_win_64.zip .\NhekoRelease\* - ls -lh build\Release\ - ls -lh NhekoRelease\ @@ -135,15 +107,22 @@ on_success: - if "%APPVEYOR_REPO_TAG%" == "true" (curl -T nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe -uredsky17:%BINTRAY_APIKEY% https://api.bintray.com/content/nheko-reborn/nheko/%APPVEYOR_REPO_TAG_NAME%/nheko/%APPVEYOR_REPO_TAG_NAME%/) deploy: - description: "Development builds" - provider: GitHub - auth_token: - secure: "ShStWeqp+TkYqJPQr7uFZb+B8ZTgC7Iwth+IkhjfRDCTLhy8gtWvlPzlQilder3E" - artifact: nheko-${APPVEYOR_REPO_TAG_NAME}-installer.exe - force_update: true - prerelease: true - on: - appveyor_repo_tag: true + - description: "Development builds" + provider: GitHub + auth_token: + secure: "ShStWeqp+TkYqJPQr7uFZb+B8ZTgC7Iwth+IkhjfRDCTLhy8gtWvlPzlQilder3E" + artifact: nheko-${APPVEYOR_REPO_TAG_NAME}-installer.exe + force_update: true + prerelease: true + on: + appveyor_repo_tag: true + - provider: S3 + access_key_id: ${AWS_ACCESS_KEY} + secret_access_key: ${AWS_SECRET_KEY} + bucket: ${AWS_BUCKET_NAME} + region: ${AWS_DEFAULT_REGION} + set_public: true + artifact: nheko-$(APPVEYOR_REPO_TAG_NAME)-installer.exe, nheko_win_64.zip artifacts: - path: nheko_win_64.zip diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake new file mode 100644 index 00000000..372dc7a5 --- /dev/null +++ b/cmake/FindLMDB.cmake @@ -0,0 +1,15 @@ +# +# Find the lmdb library & include dir. +# + +find_path (LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "$ENV{LMDB_DIR}/include") +find_library (LMDB_LIBRARY NAMES lmdb PATHS "$ENV{LMDB_DIR}/lib" ) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LMDB DEFAULT_MSG LMDB_INCLUDE_DIR LMDB_LIBRARY) + + +add_library(lmdb INTERFACE IMPORTED GLOBAL) +target_include_directories(lmdb INTERFACE ${LMDB_INCLUDE_DIR}) +target_link_libraries(lmdb INTERFACE ${LMDB_LIBRARY}) + +add_library(liblmdb::lmdb ALIAS lmdb) diff --git a/cmake/FindOlm.cmake b/cmake/FindOlm.cmake deleted file mode 100644 index da68dbf5..00000000 --- a/cmake/FindOlm.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# -# CMake module to search for the olm library -# -# On success, the macro sets the following variables: -# OLM_FOUND = if the library found -# OLM_LIBRARY = full path to the library -# OLM_INCLUDE_DIR = where to find the library headers -# -if(WIN32) - message(STATUS "FindOlm is not supported in Windows") - return() -endif() - -find_path(OLM_INCLUDE_DIR - NAMES olm/olm.h - PATHS /usr/include - /usr/local/include - $ENV{LIB_DIR}/include - $ENV{LIB_DIR}/include/olm) - -find_library(OLM_LIBRARY - NAMES olm - PATHS /usr/lib /usr/local/lib $ENV{LIB_DIR}/lib) - -if(OLM_FOUND) - set(OLM_INCLUDE_DIRS ${OLM_INCLUDE_DIR}) - - if(NOT OLM_LIBRARIES) - set(OLM_LIBRARIES ${OLM_LIBRARY}) - endif() -endif() - -if(NOT TARGET Olm::Olm) - add_library(Olm::Olm UNKNOWN IMPORTED) - set_target_properties(Olm::Olm - PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - ${OLM_INCLUDE_DIR}) - set_property(TARGET Olm::Olm APPEND PROPERTY IMPORTED_LOCATION ${OLM_LIBRARY}) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(OLM DEFAULT_MSG OLM_INCLUDE_DIR OLM_LIBRARY) - -mark_as_advanced(OLM_LIBRARY OLM_INCLUDE_DIR) diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake new file mode 100644 index 00000000..d2f87774 --- /dev/null +++ b/cmake/Hunter/config.cmake @@ -0,0 +1,5 @@ +hunter_config( + Boost + VERSION "1.70.0-p0" + CMAKE_ARGS IOSTREAMS_NO_BZIP2=1 +) diff --git a/cmake/HunterGate.cmake b/cmake/HunterGate.cmake new file mode 100644 index 00000000..e78d3e89 --- /dev/null +++ b/cmake/HunterGate.cmake @@ -0,0 +1,528 @@ +# Copyright (c) 2013-2019, Ruslan Baratov +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This is a gate file to Hunter package manager. +# Include this file using `include` command and add package you need, example: +# +# cmake_minimum_required(VERSION 3.2) +# +# include("cmake/HunterGate.cmake") +# HunterGate( +# URL "https://github.com/path/to/hunter/archive.tar.gz" +# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" +# ) +# +# project(MyProject) +# +# hunter_add_package(Foo) +# hunter_add_package(Boo COMPONENTS Bar Baz) +# +# Projects: +# * https://github.com/hunter-packages/gate/ +# * https://github.com/ruslo/hunter + +option(HUNTER_ENABLED "Enable Hunter package manager support" ON) + +if(HUNTER_ENABLED) + if(CMAKE_VERSION VERSION_LESS "3.2") + message( + FATAL_ERROR + "At least CMake version 3.2 required for Hunter dependency management." + " Update CMake or set HUNTER_ENABLED to OFF." + ) + endif() +endif() + +include(CMakeParseArguments) # cmake_parse_arguments + +option(HUNTER_STATUS_PRINT "Print working status" ON) +option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) +option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) + +set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors") + +function(hunter_gate_status_print) + if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + message(STATUS "[hunter] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_status_debug) + if(HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + string(TIMESTAMP timestamp) + message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_error_page error_page) + message("------------------------------ ERROR ------------------------------") + message(" ${HUNTER_ERROR_PAGE}/${error_page}.html") + message("-------------------------------------------------------------------") + message("") + message(FATAL_ERROR "") +endfunction() + +function(hunter_gate_internal_error) + message("") + foreach(print_message ${ARGV}) + message("[hunter ** INTERNAL **] ${print_message}") + endforeach() + message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_error_page("error.internal") +endfunction() + +function(hunter_gate_fatal_error) + cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}") + if("${hunter_ERROR_PAGE}" STREQUAL "") + hunter_gate_internal_error("Expected ERROR_PAGE") + endif() + message("") + foreach(x ${hunter_UNPARSED_ARGUMENTS}) + message("[hunter ** FATAL ERROR **] ${x}") + endforeach() + message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_error_page("${hunter_ERROR_PAGE}") +endfunction() + +function(hunter_gate_user_error) + hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data") +endfunction() + +function(hunter_gate_self root version sha1 result) + string(COMPARE EQUAL "${root}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("root is empty") + endif() + + string(COMPARE EQUAL "${version}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("version is empty") + endif() + + string(COMPARE EQUAL "${sha1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("sha1 is empty") + endif() + + string(SUBSTRING "${sha1}" 0 7 archive_id) + + set( + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + ) + + set("${result}" "${hunter_self}" PARENT_SCOPE) +endfunction() + +# Set HUNTER_GATE_ROOT cmake variable to suitable value. +function(hunter_gate_detect_root) + # Check CMake variable + string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") + return() + endif() + + # Check environment variable + string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") + return() + endif() + + # Check HOME environment variable + string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") + return() + endif() + + # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) + if(WIN32) + string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using SYSTEMDRIVE environment variable" + ) + return() + endif() + + string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using USERPROFILE environment variable" + ) + return() + endif() + endif() + + hunter_gate_fatal_error( + "Can't detect HUNTER_ROOT" + ERROR_PAGE "error.detect.hunter.root" + ) +endfunction() + +function(hunter_gate_download dir) + string( + COMPARE + NOTEQUAL + "$ENV{HUNTER_DISABLE_AUTOINSTALL}" + "" + disable_autoinstall + ) + if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) + hunter_gate_fatal_error( + "Hunter not found in '${dir}'" + "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" + "Settings:" + " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" + " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" + ERROR_PAGE "error.run.install" + ) + endif() + string(COMPARE EQUAL "${dir}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("Empty 'dir' argument") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_URL empty") + endif() + + set(done_location "${dir}/DONE") + set(sha1_location "${dir}/SHA1") + + set(build_dir "${dir}/Build") + set(cmakelists "${dir}/CMakeLists.txt") + + hunter_gate_status_debug("Locking directory: ${dir}") + file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) + hunter_gate_status_debug("Lock done") + + if(EXISTS "${done_location}") + # while waiting for lock other instance can do all the job + hunter_gate_status_debug("File '${done_location}' found, skip install") + return() + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(MAKE_DIRECTORY "${build_dir}") # check directory permissions + + # Disabling languages speeds up a little bit, reduces noise in the output + # and avoids path too long windows error + file( + WRITE + "${cmakelists}" + "cmake_minimum_required(VERSION 3.2)\n" + "project(HunterDownload LANGUAGES NONE)\n" + "include(ExternalProject)\n" + "ExternalProject_Add(\n" + " Hunter\n" + " URL\n" + " \"${HUNTER_GATE_URL}\"\n" + " URL_HASH\n" + " SHA1=${HUNTER_GATE_SHA1}\n" + " DOWNLOAD_DIR\n" + " \"${dir}\"\n" + " TLS_VERIFY\n" + " ${HUNTER_TLS_VERIFY}\n" + " SOURCE_DIR\n" + " \"${dir}/Unpacked\"\n" + " CONFIGURE_COMMAND\n" + " \"\"\n" + " BUILD_COMMAND\n" + " \"\"\n" + " INSTALL_COMMAND\n" + " \"\"\n" + ")\n" + ) + + if(HUNTER_STATUS_DEBUG) + set(logging_params "") + else() + set(logging_params OUTPUT_QUIET) + endif() + + hunter_gate_status_debug("Run generate") + + # Need to add toolchain file too. + # Otherwise on Visual Studio + MDD this will fail with error: + # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" + if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") + get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") + else() + # 'toolchain_arg' can't be empty + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") + endif() + + string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) + if(no_make) + set(make_arg "") + else() + # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM + set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") + endif() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-H${dir}" + "-B${build_dir}" + "-G${CMAKE_GENERATOR}" + "${toolchain_arg}" + ${make_arg} + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error( + "Configure project failed." + "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" + "In directory ${dir}" + ) + endif() + + hunter_gate_status_print( + "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" + " ${HUNTER_GATE_URL}" + " -> ${dir}" + ) + execute_process( + COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error("Build project failed") + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") + file(WRITE "${done_location}" "DONE") + + hunter_gate_status_debug("Finished") +endfunction() + +# Must be a macro so master file 'cmake/Hunter' can +# apply all variables easily just by 'include' command +# (otherwise PARENT_SCOPE magic needed) +macro(HunterGate) + if(HUNTER_GATE_DONE) + # variable HUNTER_GATE_DONE set explicitly for external project + # (see `hunter_download`) + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() + + # First HunterGate command will init Hunter, others will be ignored + get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) + + if(NOT HUNTER_ENABLED) + # Empty function to avoid error "unknown function" + function(hunter_add_package) + endfunction() + + set( + _hunter_gate_disabled_mode_dir + "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" + ) + if(EXISTS "${_hunter_gate_disabled_mode_dir}") + hunter_gate_status_debug( + "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" + ) + list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") + endif() + elseif(_hunter_gate_done) + hunter_gate_status_debug("Secondary HunterGate (use old settings)") + hunter_gate_self( + "${HUNTER_CACHED_ROOT}" + "${HUNTER_VERSION}" + "${HUNTER_SHA1}" + _hunter_self + ) + include("${_hunter_self}/cmake/Hunter") + else() + set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") + + string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) + if(_have_project_name) + hunter_gate_fatal_error( + "Please set HunterGate *before* 'project' command. " + "Detected project: ${PROJECT_NAME}" + ERROR_PAGE "error.huntergate.before.project" + ) + endif() + + cmake_parse_arguments( + HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} + ) + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) + string( + COMPARE + NOTEQUAL + "${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "" + _have_unparsed + ) + string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) + string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) + + if(_have_unparsed) + hunter_gate_user_error( + "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" + ) + endif() + if(_empty_sha1) + hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") + endif() + if(_empty_url) + hunter_gate_user_error("URL suboption of HunterGate is mandatory") + endif() + if(_have_global) + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") + endif() + endif() + if(HUNTER_GATE_LOCAL) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") + endif() + endif() + if(_have_filepath) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") + endif() + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") + endif() + endif() + + hunter_gate_detect_root() # set HUNTER_GATE_ROOT + + # Beautify path, fix probable problems with windows path slashes + get_filename_component( + HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE + ) + hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") + if(NOT HUNTER_ALLOW_SPACES_IN_PATH) + string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) + if(NOT _contain_spaces EQUAL -1) + hunter_gate_fatal_error( + "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." + "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" + "(Use at your own risk!)" + ERROR_PAGE "error.spaces.in.hunter.root" + ) + endif() + endif() + + string( + REGEX + MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" + HUNTER_GATE_VERSION + "${HUNTER_GATE_URL}" + ) + string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) + if(_is_empty) + set(HUNTER_GATE_VERSION "unknown") + endif() + + hunter_gate_self( + "${HUNTER_GATE_ROOT}" + "${HUNTER_GATE_VERSION}" + "${HUNTER_GATE_SHA1}" + _hunter_self + ) + + set(_master_location "${_hunter_self}/cmake/Hunter") + get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) + set(_done_location "${_archive_id_location}/DONE") + set(_sha1_location "${_archive_id_location}/SHA1") + + # Check Hunter already downloaded by HunterGate + if(NOT EXISTS "${_done_location}") + hunter_gate_download("${_archive_id_location}") + endif() + + if(NOT EXISTS "${_done_location}") + hunter_gate_internal_error("hunter_gate_download failed") + endif() + + if(NOT EXISTS "${_sha1_location}") + hunter_gate_internal_error("${_sha1_location} not found") + endif() + file(READ "${_sha1_location}" _sha1_value) + string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + if(NOT _is_equal) + hunter_gate_internal_error( + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" + ) + endif() + if(NOT EXISTS "${_master_location}") + hunter_gate_user_error( + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" + ) + endif() + include("${_master_location}") + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() +endmacro() diff --git a/cmake/LMDB.cmake b/cmake/LMDB.cmake deleted file mode 100644 index 2d8184de..00000000 --- a/cmake/LMDB.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# -# Find the lmdb library & include dir. -# Build lmdb on Appveyor. -# - -if(APPVEYOR_BUILD) - set(LMDB_VERSION "LMDB_0.9.21") - set(NTDLIB "C:/WINDDK/7600.16385.1/lib/win7/amd64/ntdll.lib") - - execute_process( - COMMAND git clone --depth=1 --branch ${LMDB_VERSION} https://github.com/LMDB/lmdb) - - set(LMDB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb) - - add_library(lmdb - ${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/lmdb.h - ${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/mdb.c - ${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/midl.h - ${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/midl.c) - - set(LMDB_LIBRARY lmdb) -else() - find_path (LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "$ENV{LMDB_DIR}/include") - find_library (LMDB_LIBRARY NAMES lmdb PATHS "$ENV{LMDB_DIR}/lib" ) - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(LMDB DEFAULT_MSG LMDB_INCLUDE_DIR LMDB_LIBRARY) -endif() - -include_directories(${LMDB_INCLUDE_DIR}) diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake index 8ca91883..16120219 100644 --- a/cmake/Translations.cmake +++ b/cmake/Translations.cmake @@ -21,4 +21,8 @@ if(NOT EXISTS ${_qrc}) endif() qt5_add_resources(LANG_QRC ${_qrc}) -qt5_add_resources(QRC resources/res.qrc) +if(Qt5QuickCompiler_FOUND) + qtquick_compiler_add_resources(QRC resources/res.qrc) +else() + qt5_add_resources(QRC resources/res.qrc) +endif() diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt deleted file mode 100644 index 6f8f2616..00000000 --- a/deps/CMakeLists.txt +++ /dev/null @@ -1,125 +0,0 @@ -cmake_minimum_required(VERSION 3.11) -project(NHEKO_DEPS) - -# Point CMake at any custom modules we may ship -list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) -endif() - -set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" - CACHE PATH "Dependencies install directory.") -set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin" - CACHE PATH "Dependencies binary install directory.") -set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib" - CACHE PATH "Dependencies library install directory.") -set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build" - CACHE PATH "Dependencies build directory.") -set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads" - CACHE PATH "Dependencies download directory.") - -option(USE_BUNDLED "Use bundled dependencies." ON) - -option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${USE_BUNDLED}) -option(USE_BUNDLED_CMARK "Use the bundled version of cmark." ${USE_BUNDLED}) -option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog." ${USE_BUNDLED}) -option(USE_BUNDLED_OLM "Use the bundled version of libolm." ${USE_BUNDLED}) -option(USE_BUNDLED_TWEENY "Use the bundled version of Tweeny." ${USE_BUNDLED}) -option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdbxx." ${USE_BUNDLED}) -option(USE_BUNDLED_MATRIX_CLIENT "Use the bundled version of mtxclient." - ${USE_BUNDLED}) -option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json." ${USE_BUNDLED}) -option(MTX_STATIC "Compile / link bundled mtx client statically" OFF) - -if(USE_BUNDLED_BOOST) - # bundled boost is 1.68, which requires CMake 3.12 or greater. - cmake_minimum_required(VERSION 3.12) -endif() - -include(ExternalProject) - -set(BOOST_URL - https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2) -set(BOOST_SHA256 - 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406) - -set( - MTXCLIENT_URL - https://github.com/Nheko-Reborn/mtxclient/archive/975ce8906c42742dbb698fcf9fa15663c530df20.tar.gz) -set(MTXCLIENT_HASH - 5e3169ef19b6e585069ceced42489574ce18380480628339bac015759fa1893e) -set( - TWEENY_URL - https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz - ) -set(TWEENY_HASH - 9a632b9da84823fae002ad5d9ba02c8d77c0a3810479974c6b637c5504165475) - -set( - LMDBXX_HEADER_URL - https://raw.githubusercontent.com/bendiken/lmdbxx/0b43ca87d8cfabba392dfe884eb1edb83874de02/lmdb%2B%2B.h - ) -set(LMDBXX_HASH - c57b501a4e8fa1187fa7fd348da415c7685a50a7cb25b17b3f257b9e9426f73d) - -set(OLM_URL https://gitlab.matrix.org/matrix-org/olm.git) -set(OLM_TAG 4065c8e11a33ba41133a086ed3de4da94dcb6bae) - -set(CMARK_URL https://github.com/commonmark/cmark/archive/0.28.3.tar.gz) -set(CMARK_HASH acc98685d3c1b515ff787ac7c994188dadaf28a2d700c10c1221da4199bae1fc) - -set(SPDLOG_URL https://github.com/gabime/spdlog/archive/v1.1.0.tar.gz) -set(SPDLOG_HASH - 3dbcbfd8c07e25f5e0d662b194d3a7772ef214358c49ada23c044c4747ce8b19) - -set(JSON_URL - https://github.com/nlohmann/json.git) -set(JSON_TAG - v3.2.0) - -if(USE_BUNDLED_JSON) - include(Json) -endif() - -if(USE_BUNDLED_BOOST) - include(Boost) -endif() - -if(USE_BUNDLED_SPDLOG) - include(SpdLog) -endif() - -if(USE_BUNDLED_OLM) - include(Olm) -endif() - -if(USE_BUNDLED_CMARK) - include(cmark) -endif() - -if(USE_BUNDLED_TWEENY) - include(Tweeny) -endif() - -if(USE_BUNDLED_LMDBXX) - file(DOWNLOAD ${LMDBXX_HEADER_URL} ${DEPS_INSTALL_DIR}/include/lmdb++.h - EXPECTED_HASH SHA256=${LMDBXX_HASH}) -endif() - -if(WIN32) - if("${TARGET_ARCH}" STREQUAL "X86_64") - set(TARGET_ARCH x64) - elseif(TARGET_ARCH STREQUAL "X86") - set(TARGET_ARCH ia32) - endif() -endif() - -add_custom_target(third-party ALL - COMMAND ${CMAKE_COMMAND} -E touch .third-party - DEPENDS ${THIRD_PARTY_DEPS}) - -if(USE_BUNDLED_MATRIX_CLIENT) - include(MatrixClient) - add_dependencies(MatrixClient third-party) -endif() diff --git a/deps/cmake/Boost.cmake b/deps/cmake/Boost.cmake deleted file mode 100644 index 47eb723b..00000000 --- a/deps/cmake/Boost.cmake +++ /dev/null @@ -1,23 +0,0 @@ -if(WIN32) - message(STATUS "Building Boost in Windows is not supported (skipping)") - return() -endif() - -ExternalProject_Add( - Boost - - URL ${BOOST_URL} - URL_HASH SHA256=${BOOST_SHA256} - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/boost - DOWNLOAD_NO_PROGRESS 0 - - BUILD_IN_SOURCE 1 - SOURCE_DIR ${DEPS_BUILD_DIR}/boost - CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/boost/bootstrap.sh - --with-libraries=random,thread,system,iostreams,atomic,chrono,date_time,regex - --prefix=${DEPS_INSTALL_DIR} - BUILD_COMMAND ${DEPS_BUILD_DIR}/boost/b2 -d0 cxxstd=14 variant=release link=shared runtime-link=shared threading=multi --layout=system - INSTALL_COMMAND ${DEPS_BUILD_DIR}/boost/b2 -d0 install -) - -list(APPEND THIRD_PARTY_DEPS Boost) diff --git a/deps/cmake/Json.cmake b/deps/cmake/Json.cmake deleted file mode 100644 index 3b63550e..00000000 --- a/deps/cmake/Json.cmake +++ /dev/null @@ -1,19 +0,0 @@ -ExternalProject_Add( - Json - - GIT_REPOSITORY ${JSON_URL} - GIT_TAG ${JSON_TAG} - - BUILD_IN_SOURCE 1 - SOURCE_DIR ${DEPS_BUILD_DIR}/json - - CONFIGURE_COMMAND ${CMAKE_COMMAND} - -DJSON_BuildTests=OFF - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - - BUILD_COMMAND ${CMAKE_COMMAND} --build ${DEPS_BUILD_DIR}/json - INSTALL_COMMAND make install -) - -list(APPEND THIRD_PARTY_DEPS Json) diff --git a/deps/cmake/MatrixClient.cmake b/deps/cmake/MatrixClient.cmake deleted file mode 100644 index 44992c0b..00000000 --- a/deps/cmake/MatrixClient.cmake +++ /dev/null @@ -1,43 +0,0 @@ -set(PLATFORM_FLAGS "") - -if(MSVC) - set(PLATFORM_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64") -endif() - -if(APPLE) - set(PLATFORM_FLAGS "-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl") -endif() - -# Force to build with the bundled version of Boost. This is necessary because -# if an outdated version of Boost is installed, then CMake will grab that -# instead of the bundled version of Boost, like we wanted. -set(BOOST_BUNDLE_ROOT "-DBOOST_ROOT=${DEPS_BUILD_DIR}/boost") - -set (MTX_SHARED ON) - -if (MTX_STATIC) - set (MTX_SHARED OFF) -endif() - -ExternalProject_Add( - MatrixClient - - URL ${MTXCLIENT_URL} - URL_HASH SHA256=${MTXCLIENT_HASH} - - BUILD_IN_SOURCE 1 - SOURCE_DIR ${DEPS_BUILD_DIR}/mtxclient - CONFIGURE_COMMAND ${CMAKE_COMMAND} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_BUILD_TYPE=Release - -DBUILD_LIB_TESTS=OFF - -DBUILD_LIB_EXAMPLES=OFF - -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - ${BOOST_BUNDLE_ROOT} - -DBUILD_SHARED_LIBS=${MTX_SHARED} - ${PLATFORM_FLAGS} - ${DEPS_BUILD_DIR}/mtxclient - BUILD_COMMAND - ${CMAKE_COMMAND} --build ${DEPS_BUILD_DIR}/mtxclient --config Release) - -list(APPEND THIRD_PARTY_DEPS MatrixClient) diff --git a/deps/cmake/Olm.cmake b/deps/cmake/Olm.cmake deleted file mode 100644 index a5b8be76..00000000 --- a/deps/cmake/Olm.cmake +++ /dev/null @@ -1,34 +0,0 @@ -set(WINDOWS_FLAGS "") - -if(MSVC) - set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64") -endif() - -ExternalProject_Add( - Olm - - GIT_REPOSITORY ${OLM_URL} - GIT_TAG ${OLM_TAG} - - BUILD_IN_SOURCE 1 - SOURCE_DIR ${DEPS_BUILD_DIR}/olm - CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/OlmCMakeLists.txt - ${DEPS_BUILD_DIR}/olm/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/OlmConfig.cmake.in - ${DEPS_BUILD_DIR}/olm/cmake/OlmConfig.cmake.in - COMMAND ${CMAKE_COMMAND} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_BUILD_TYPE=Release - ${DEPS_BUILD_DIR}/olm - ${WINDOWS_FLAGS} - BUILD_COMMAND ${CMAKE_COMMAND} - --build ${DEPS_BUILD_DIR}/olm - --config Release - INSTALL_COMMAND ${CMAKE_COMMAND} - --build ${DEPS_BUILD_DIR}/olm - --config Release - --target install) - -list(APPEND THIRD_PARTY_DEPS Olm) diff --git a/deps/cmake/OlmCMakeLists.txt b/deps/cmake/OlmCMakeLists.txt deleted file mode 100644 index 529cbb92..00000000 --- a/deps/cmake/OlmCMakeLists.txt +++ /dev/null @@ -1,107 +0,0 @@ -cmake_minimum_required(VERSION 3.1) - -project(olm VERSION 2.2.2 LANGUAGES CXX C) - -add_definitions(-DOLMLIB_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) -add_definitions(-DOLMLIB_VERSION_MINOR=${PROJECT_VERSION_MINOR}) -add_definitions(-DOLMLIB_VERSION_PATCH=${PROJECT_VERSION_PATCH}) - -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_C_STANDARD 99) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) -endif() - -add_library(olm - src/account.cpp - src/base64.cpp - src/cipher.cpp - src/crypto.cpp - src/memory.cpp - src/message.cpp - src/pickle.cpp - src/ratchet.cpp - src/session.cpp - src/utility.cpp - - src/ed25519.c - src/error.c - src/inbound_group_session.c - src/megolm.c - src/olm.cpp - src/outbound_group_session.c - src/pickle_encoding.c - - lib/crypto-algorithms/aes.c - lib/crypto-algorithms/sha256.c - lib/curve25519-donna/curve25519-donna.c) -add_library(Olm::Olm ALIAS olm) - -target_include_directories(olm - PUBLIC - $ - $ - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/lib) - -set_target_properties(olm PROPERTIES - SOVERSION ${PROJECT_VERSION_MAJOR} - VERSION ${PROJECT_VERSION}) - -set_target_properties(olm PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR} - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) - -# -# Installation -# -include(GNUInstallDirs) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/Olm) -install(TARGETS olm - EXPORT olm-targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -# The exported target will be named Olm. -set_target_properties(olm PROPERTIES EXPORT_NAME Olm) -install(FILES - ${CMAKE_SOURCE_DIR}/include/olm/olm.h - ${CMAKE_SOURCE_DIR}/include/olm/outbound_group_session.h - ${CMAKE_SOURCE_DIR}/include/olm/inbound_group_session.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/olm) - -# Export the targets to a script. -install(EXPORT olm-targets - FILE OlmTargets.cmake - NAMESPACE Olm:: - DESTINATION ${INSTALL_CONFIGDIR}) - -# Create a ConfigVersion.cmake file. -include(CMakePackageConfigHelpers) -write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/OlmConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - -configure_package_config_file( - ${CMAKE_CURRENT_LIST_DIR}/cmake/OlmConfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/OlmConfig.cmake - INSTALL_DESTINATION ${INSTALL_CONFIGDIR}) - -#Install the config & configversion. -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/OlmConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/OlmConfigVersion.cmake - DESTINATION ${INSTALL_CONFIGDIR}) - -# Register package in user's package registry -export(EXPORT olm-targets - FILE ${CMAKE_CURRENT_BINARY_DIR}/OlmTargets.cmake - NAMESPACE Olm::) -export(PACKAGE Olm) diff --git a/deps/cmake/OlmConfig.cmake.in b/deps/cmake/OlmConfig.cmake.in deleted file mode 100644 index b670fe85..00000000 --- a/deps/cmake/OlmConfig.cmake.in +++ /dev/null @@ -1,11 +0,0 @@ -get_filename_component(Olm_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -include(CMakeFindDependencyMacro) - -list(APPEND CMAKE_MODULE_PATH ${Olm_CMAKE_DIR}) -list(REMOVE_AT CMAKE_MODULE_PATH -1) - -if(NOT TARGET Olm::Olm) - include("${Olm_CMAKE_DIR}/OlmTargets.cmake") -endif() - -set(Olm_LIBRARIES Olm::Olm) diff --git a/deps/cmake/SpdLog.cmake b/deps/cmake/SpdLog.cmake deleted file mode 100644 index 27109b66..00000000 --- a/deps/cmake/SpdLog.cmake +++ /dev/null @@ -1,23 +0,0 @@ -set(WINDOWS_FLAGS "") - -if(MSVC) - set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64") -endif() - -ExternalProject_Add( - SpdLog - - URL ${SPDLOG_URL} - URL_HASH SHA256=${SPDLOG_HASH} - - BUILD_IN_SOURCE 1 - SOURCE_DIR ${DEPS_BUILD_DIR}/spdlog - CONFIGURE_COMMAND ${CMAKE_COMMAND} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DSPDLOG_BUILD_EXAMPLES=0 - -DSPDLOG_BUILD_BENCH=0 - -DSPDLOG_BUILD_TESTING=0 - ${DEPS_BUILD_DIR}/spdlog - ${WINDOWS_FLAGS}) - -list(APPEND THIRD_PARTY_DEPS SpdLog) diff --git a/deps/cmake/Tweeny.cmake b/deps/cmake/Tweeny.cmake deleted file mode 100644 index 5a4303f9..00000000 --- a/deps/cmake/Tweeny.cmake +++ /dev/null @@ -1,22 +0,0 @@ -set(WINDOWS_FLAGS "") - -if(MSVC) - set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64") -endif() - -ExternalProject_Add( - Tweeny - - URL ${TWEENY_URL} - URL_HASH SHA256=${TWEENY_HASH} - - BUILD_IN_SOURCE 1 - SOURCE_DIR ${DEPS_BUILD_DIR}/tweeny - CONFIGURE_COMMAND ${CMAKE_COMMAND} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DTWEENY_BUILD_EXAMPLES=OFF - -DTWEENY_BUILD_DOCUMENTATION=OFF - ${DEPS_BUILD_DIR}/tweeny - ${WINDOWS_FLAGS}) - -list(APPEND THIRD_PARTY_DEPS Tweeny) diff --git a/deps/cmake/cmark.cmake b/deps/cmake/cmark.cmake deleted file mode 100644 index e0d45e88..00000000 --- a/deps/cmake/cmark.cmake +++ /dev/null @@ -1,21 +0,0 @@ -set(WINDOWS_FLAGS "") - -if(MSVC) - set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64") -endif() - -ExternalProject_Add( - cmark - - URL ${CMARK_URL} - URL_HASH SHA256=${CMARK_HASH} - - BUILD_IN_SOURCE 0 - SOURCE_DIR ${DEPS_BUILD_DIR}/cmark - CONFIGURE_COMMAND ${CMAKE_COMMAND} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMARK_TESTS=OFF - ${DEPS_BUILD_DIR}/cmark - ${WINDOWS_FLAGS}) - -list(APPEND THIRD_PARTY_DEPS cmark) diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json new file mode 100644 index 00000000..ddc1f1a0 --- /dev/null +++ b/io.github.NhekoReborn.Nheko.json @@ -0,0 +1,194 @@ +{ + "id": "io.github.NhekoReborn.Nheko", + "command": "nheko", + "branch": "0.7.0-dev", + "runtime": "org.kde.Platform", + "runtime-version": "5.14", + "sdk": "org.kde.Sdk", + "rename-icon": "nheko", + "rename-desktop-file": "nheko.desktop", + "rename-appdata-file": "nheko.appdata.xml", + "finish-args": [ + "--device=dri", + "--filesystem=home", + "--share=ipc", + "--share=network", + "--socket=pulseaudio", + "--socket=wayland", + "--socket=x11", + "--talk-name=org.freedesktop.Notifications", + "--talk-name=org.kde.StatusNotifierWatcher" + ], + "cleanup": [ + "/include", + "/bin/mdb*", + "*.a" + ], + "build-options" : { + "arch": { + "aarch64": { + "cxxflags": "-DBOOST_ASIO_DISABLE_EPOLL" + } + } + }, + "modules": [ + { + "name": "lmdb", + "sources": [ + { + "sha256": "f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28", + "type": "archive", + "url": "https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz" + } + ], + "make-install-args": [ + "prefix=/app" + ], + "no-autogen": true, + "subdir": "libraries/liblmdb" + }, + { + "name": "cmark", + "buildsystem": "cmake-ninja", + "builddir": true, + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DCMARK_TESTS=OFF" + ], + "sources": [ + { + "sha256": "2558ace3cbeff85610de3bda32858f722b359acdadf0c4691851865bb84924a6", + "type": "archive", + "url": "https://github.com/commonmark/cmark/archive/0.29.0.tar.gz" + } + ] + }, + { + "name": "spdlog", + "buildsystem": "cmake-ninja", + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DSPDLOG_BUILD_EXAMPLES=0", + "-DSPDLOG_BUILD_BENCH=0", + "-DSPDLOG_BUILD_TESTING=0" + ], + "sources": [ + { + "sha256": "3dbcbfd8c07e25f5e0d662b194d3a7772ef214358c49ada23c044c4747ce8b19", + "type": "archive", + "url": "https://github.com/gabime/spdlog/archive/v1.1.0.tar.gz" + } + ] + }, + { + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release" + ], + "buildsystem": "cmake-ninja", + "name": "olm", + "sources": [ + { + "commit": "6753595300767dd70150831dbbe6f92d64e75038", + "disable-shallow-clone": true, + "tag": "3.1.4", + "type": "git", + "url": "https://gitlab.matrix.org/matrix-org/olm.git" + } + ] + }, + { + "config-opts":[ + "-DJSON_BuildTests=OFF" + ], + "buildsystem":"cmake", + "name": "nlohmann", + "sources":[ + { + "sha256": "d51a3a8d3efbb1139d7608e28782ea9efea7e7933157e8ff8184901efd8ee760", + "type": "archive", + "url": "https://github.com/nlohmann/json/archive/v3.7.0.tar.gz" + } + ] + }, + { + "name": "sodium", + "sources": [ + { + "sha256": "6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1", + "type": "archive", + "url": "https://github.com/jedisct1/libsodium/releases/download/1.0.18-RELEASE/libsodium-1.0.18.tar.gz" + } + ] + }, + { + "build-commands": [ + "./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app", + "./b2 -d0 variant=release link=static threading=multi --layout=system", + "./b2 -d0 install" + ], + "buildsystem": "simple", + "name": "boost", + "sources": [ + { + "sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722", + "type": "archive", + "url": "https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2" + } + ] + }, + { + "config-opts": [ + "-DBUILD_LIB_TESTS=OFF", + "-DBUILD_LIB_EXAMPLES=OFF", + "-DCMAKE_BUILD_TYPE=Release", + "-DBUILD_SHARED_LIBS=OFF" + ], + "buildsystem": "cmake-ninja", + "name": "mtxclient", + "sources": [ + { + "sha256": "df3fe7e3d59b5fc52ee3ca9a132a55fc325aa799c676e9e420073c56daeb1848", + "type": "archive", + "url": "https://github.com/Nheko-Reborn/mtxclient/archive/5838f607d0e4c7595439249e8b9c213aec0667e9.tar.gz" + } + ] + }, + { + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DTWEENY_BUILD_DOCUMENTATION=OFF", + "-DTWEENY_BUILD_EXAMPLES=OFF" + ], + "buildsystem": "cmake-ninja", + "name": "tweeny", + "sources": [ + { + "sha256": "482857256a7235646004682912badb6521d361ed6987c8ebdae7986bf64ce694", + "type": "archive", + "url": "https://github.com/mobius3/tweeny/archive/43f4130f7e4a67c19d870b60864bc2862c19b81f.tar.gz" + } + ] + }, + { + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx" + ], + "buildsystem": "cmake-ninja", + "name": "nheko", + "sources": [ + { + "path": ".", + "type": "dir", + "skip": ["build-flatpak"] + }, + { + "dest": ".deps/lmdbxx", + "sha256": "93721132bbf5045d38ad62de2997655e9984c48ea5c9886746d42128f4b26fbd", + "type": "archive", + "url": "https://github.com/bendiken/lmdbxx/archive/0b43ca87d8cfabba392dfe884eb1edb83874de02.tar.gz" + } + ] + } + ] +} diff --git a/resources/emoji-test.txt b/resources/emoji-test.txt new file mode 100644 index 00000000..cadaf6cb --- /dev/null +++ b/resources/emoji-test.txt @@ -0,0 +1,4446 @@ +# emoji-test.txt +# Date: 2019-11-07, 19:33:54 GMT +# © 2019 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# Emoji Keyboard/Display Test Data for UTS #51 +# Version: 13.0 +# +# For documentation and usage, see http://www.unicode.org/reports/tr51 +# +# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. +# Format: code points; status # emoji name +# Code points — list of one or more hex code points, separated by spaces +# Status +# component — an Emoji_Component, +# excluding Regional_Indicators, ASCII, and non-Emoji. +# fully-qualified — a fully-qualified emoji (see ED-18 in UTS #51), +# excluding Emoji_Component +# minimally-qualified — a minimally-qualified emoji (see ED-18a in UTS #51) +# unqualified — a unqualified emoji (See ED-19 in UTS #51) +# Notes: +# • This includes the emoji components that need emoji presentation (skin tone and hair) +# when isolated, but omits the components that need not have an emoji +# presentation when isolated. +# • The RGI set is covered by the listed fully-qualified emoji. +# • The listed minimally-qualified and unqualified cover all cases where an +# element of the RGI set is missing one or more emoji presentation selectors. +# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. +# • The groups and subgroups are illustrative. See the Emoji Order chart for more information. + + +# group: Smileys & Emotion + +# subgroup: face-smiling +1F600 ; fully-qualified # 😀 E1.0 grinning face +1F603 ; fully-qualified # 😃 E0.6 grinning face with big eyes +1F604 ; fully-qualified # 😄 E0.6 grinning face with smiling eyes +1F601 ; fully-qualified # 😁 E0.6 beaming face with smiling eyes +1F606 ; fully-qualified # 😆 E0.6 grinning squinting face +1F605 ; fully-qualified # 😅 E0.6 grinning face with sweat +1F923 ; fully-qualified # 🤣 E3.0 rolling on the floor laughing +1F602 ; fully-qualified # 😂 E0.6 face with tears of joy +1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face +1F643 ; fully-qualified # 🙃 E1.0 upside-down face +1F609 ; fully-qualified # 😉 E0.6 winking face +1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes +1F607 ; fully-qualified # 😇 E1.0 smiling face with halo + +# subgroup: face-affection +1F970 ; fully-qualified # 🥰 E11.0 smiling face with hearts +1F60D ; fully-qualified # 😍 E0.6 smiling face with heart-eyes +1F929 ; fully-qualified # 🤩 E5.0 star-struck +1F618 ; fully-qualified # 😘 E0.6 face blowing a kiss +1F617 ; fully-qualified # 😗 E1.0 kissing face +263A FE0F ; fully-qualified # ☺️ E0.6 smiling face +263A ; unqualified # ☺ E0.6 smiling face +1F61A ; fully-qualified # 😚 E0.6 kissing face with closed eyes +1F619 ; fully-qualified # 😙 E1.0 kissing face with smiling eyes +1F972 ; fully-qualified # 🥲 E13.0 smiling face with tear + +# subgroup: face-tongue +1F60B ; fully-qualified # 😋 E0.6 face savoring food +1F61B ; fully-qualified # 😛 E1.0 face with tongue +1F61C ; fully-qualified # 😜 E0.6 winking face with tongue +1F92A ; fully-qualified # 🤪 E5.0 zany face +1F61D ; fully-qualified # 😝 E0.6 squinting face with tongue +1F911 ; fully-qualified # 🤑 E1.0 money-mouth face + +# subgroup: face-hand +1F917 ; fully-qualified # 🤗 E1.0 hugging face +1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth +1F92B ; fully-qualified # 🤫 E5.0 shushing face +1F914 ; fully-qualified # 🤔 E1.0 thinking face + +# subgroup: face-neutral-skeptical +1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face +1F928 ; fully-qualified # 🤨 E5.0 face with raised eyebrow +1F610 ; fully-qualified # 😐 E0.7 neutral face +1F611 ; fully-qualified # 😑 E1.0 expressionless face +1F636 ; fully-qualified # 😶 E1.0 face without mouth +1F60F ; fully-qualified # 😏 E0.6 smirking face +1F612 ; fully-qualified # 😒 E0.6 unamused face +1F644 ; fully-qualified # 🙄 E1.0 face with rolling eyes +1F62C ; fully-qualified # 😬 E1.0 grimacing face +1F925 ; fully-qualified # 🤥 E3.0 lying face + +# subgroup: face-sleepy +1F60C ; fully-qualified # 😌 E0.6 relieved face +1F614 ; fully-qualified # 😔 E0.6 pensive face +1F62A ; fully-qualified # 😪 E0.6 sleepy face +1F924 ; fully-qualified # 🤤 E3.0 drooling face +1F634 ; fully-qualified # 😴 E1.0 sleeping face + +# subgroup: face-unwell +1F637 ; fully-qualified # 😷 E0.6 face with medical mask +1F912 ; fully-qualified # 🤒 E1.0 face with thermometer +1F915 ; fully-qualified # 🤕 E1.0 face with head-bandage +1F922 ; fully-qualified # 🤢 E3.0 nauseated face +1F92E ; fully-qualified # 🤮 E5.0 face vomiting +1F927 ; fully-qualified # 🤧 E3.0 sneezing face +1F975 ; fully-qualified # 🥵 E11.0 hot face +1F976 ; fully-qualified # 🥶 E11.0 cold face +1F974 ; fully-qualified # 🥴 E11.0 woozy face +1F635 ; fully-qualified # 😵 E0.6 dizzy face +1F92F ; fully-qualified # 🤯 E5.0 exploding head + +# subgroup: face-hat +1F920 ; fully-qualified # 🤠 E3.0 cowboy hat face +1F973 ; fully-qualified # 🥳 E11.0 partying face +1F978 ; fully-qualified # 🥸 E13.0 disguised face + +# subgroup: face-glasses +1F60E ; fully-qualified # 😎 E1.0 smiling face with sunglasses +1F913 ; fully-qualified # 🤓 E1.0 nerd face +1F9D0 ; fully-qualified # 🧐 E5.0 face with monocle + +# subgroup: face-concerned +1F615 ; fully-qualified # 😕 E1.0 confused face +1F61F ; fully-qualified # 😟 E1.0 worried face +1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face +2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face +2639 ; unqualified # ☹ E0.7 frowning face +1F62E ; fully-qualified # 😮 E1.0 face with open mouth +1F62F ; fully-qualified # 😯 E1.0 hushed face +1F632 ; fully-qualified # 😲 E0.6 astonished face +1F633 ; fully-qualified # 😳 E0.6 flushed face +1F97A ; fully-qualified # 🥺 E11.0 pleading face +1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth +1F627 ; fully-qualified # 😧 E1.0 anguished face +1F628 ; fully-qualified # 😨 E0.6 fearful face +1F630 ; fully-qualified # 😰 E0.6 anxious face with sweat +1F625 ; fully-qualified # 😥 E0.6 sad but relieved face +1F622 ; fully-qualified # 😢 E0.6 crying face +1F62D ; fully-qualified # 😭 E0.6 loudly crying face +1F631 ; fully-qualified # 😱 E0.6 face screaming in fear +1F616 ; fully-qualified # 😖 E0.6 confounded face +1F623 ; fully-qualified # 😣 E0.6 persevering face +1F61E ; fully-qualified # 😞 E0.6 disappointed face +1F613 ; fully-qualified # 😓 E0.6 downcast face with sweat +1F629 ; fully-qualified # 😩 E0.6 weary face +1F62B ; fully-qualified # 😫 E0.6 tired face +1F971 ; fully-qualified # 🥱 E12.0 yawning face + +# subgroup: face-negative +1F624 ; fully-qualified # 😤 E0.6 face with steam from nose +1F621 ; fully-qualified # 😡 E0.6 pouting face +1F620 ; fully-qualified # 😠 E0.6 angry face +1F92C ; fully-qualified # 🤬 E5.0 face with symbols on mouth +1F608 ; fully-qualified # 😈 E1.0 smiling face with horns +1F47F ; fully-qualified # 👿 E0.6 angry face with horns +1F480 ; fully-qualified # 💀 E0.6 skull +2620 FE0F ; fully-qualified # ☠️ E1.0 skull and crossbones +2620 ; unqualified # ☠ E1.0 skull and crossbones + +# subgroup: face-costume +1F4A9 ; fully-qualified # 💩 E0.6 pile of poo +1F921 ; fully-qualified # 🤡 E3.0 clown face +1F479 ; fully-qualified # 👹 E0.6 ogre +1F47A ; fully-qualified # 👺 E0.6 goblin +1F47B ; fully-qualified # 👻 E0.6 ghost +1F47D ; fully-qualified # 👽 E0.6 alien +1F47E ; fully-qualified # 👾 E0.6 alien monster +1F916 ; fully-qualified # 🤖 E1.0 robot + +# subgroup: cat-face +1F63A ; fully-qualified # 😺 E0.6 grinning cat +1F638 ; fully-qualified # 😸 E0.6 grinning cat with smiling eyes +1F639 ; fully-qualified # 😹 E0.6 cat with tears of joy +1F63B ; fully-qualified # 😻 E0.6 smiling cat with heart-eyes +1F63C ; fully-qualified # 😼 E0.6 cat with wry smile +1F63D ; fully-qualified # 😽 E0.6 kissing cat +1F640 ; fully-qualified # 🙀 E0.6 weary cat +1F63F ; fully-qualified # 😿 E0.6 crying cat +1F63E ; fully-qualified # 😾 E0.6 pouting cat + +# subgroup: monkey-face +1F648 ; fully-qualified # 🙈 E0.6 see-no-evil monkey +1F649 ; fully-qualified # 🙉 E0.6 hear-no-evil monkey +1F64A ; fully-qualified # 🙊 E0.6 speak-no-evil monkey + +# subgroup: emotion +1F48B ; fully-qualified # 💋 E0.6 kiss mark +1F48C ; fully-qualified # 💌 E0.6 love letter +1F498 ; fully-qualified # 💘 E0.6 heart with arrow +1F49D ; fully-qualified # 💝 E0.6 heart with ribbon +1F496 ; fully-qualified # 💖 E0.6 sparkling heart +1F497 ; fully-qualified # 💗 E0.6 growing heart +1F493 ; fully-qualified # 💓 E0.6 beating heart +1F49E ; fully-qualified # 💞 E0.6 revolving hearts +1F495 ; fully-qualified # 💕 E0.6 two hearts +1F49F ; fully-qualified # 💟 E0.6 heart decoration +2763 FE0F ; fully-qualified # ❣️ E1.0 heart exclamation +2763 ; unqualified # ❣ E1.0 heart exclamation +1F494 ; fully-qualified # 💔 E0.6 broken heart +2764 FE0F ; fully-qualified # ❤️ E0.6 red heart +2764 ; unqualified # ❤ E0.6 red heart +1F9E1 ; fully-qualified # 🧡 E5.0 orange heart +1F49B ; fully-qualified # 💛 E0.6 yellow heart +1F49A ; fully-qualified # 💚 E0.6 green heart +1F499 ; fully-qualified # 💙 E0.6 blue heart +1F49C ; fully-qualified # 💜 E0.6 purple heart +1F90E ; fully-qualified # 🤎 E12.0 brown heart +1F5A4 ; fully-qualified # 🖤 E3.0 black heart +1F90D ; fully-qualified # 🤍 E12.0 white heart +1F4AF ; fully-qualified # 💯 E0.6 hundred points +1F4A2 ; fully-qualified # 💢 E0.6 anger symbol +1F4A5 ; fully-qualified # 💥 E0.6 collision +1F4AB ; fully-qualified # 💫 E0.6 dizzy +1F4A6 ; fully-qualified # 💦 E0.6 sweat droplets +1F4A8 ; fully-qualified # 💨 E0.6 dashing away +1F573 FE0F ; fully-qualified # 🕳️ E0.7 hole +1F573 ; unqualified # 🕳 E0.7 hole +1F4A3 ; fully-qualified # 💣 E0.6 bomb +1F4AC ; fully-qualified # 💬 E0.6 speech balloon +1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ E2.0 eye in speech bubble +1F441 200D 1F5E8 FE0F ; unqualified # 👁‍🗨️ E2.0 eye in speech bubble +1F441 FE0F 200D 1F5E8 ; unqualified # 👁️‍🗨 E2.0 eye in speech bubble +1F441 200D 1F5E8 ; unqualified # 👁‍🗨 E2.0 eye in speech bubble +1F5E8 FE0F ; fully-qualified # 🗨️ E2.0 left speech bubble +1F5E8 ; unqualified # 🗨 E2.0 left speech bubble +1F5EF FE0F ; fully-qualified # 🗯️ E0.7 right anger bubble +1F5EF ; unqualified # 🗯 E0.7 right anger bubble +1F4AD ; fully-qualified # 💭 E1.0 thought balloon +1F4A4 ; fully-qualified # 💤 E0.6 zzz + +# Smileys & Emotion subtotal: 162 +# Smileys & Emotion subtotal: 162 w/o modifiers + +# group: People & Body + +# subgroup: hand-fingers-open +1F44B ; fully-qualified # 👋 E0.6 waving hand +1F44B 1F3FB ; fully-qualified # 👋🏻 E1.0 waving hand: light skin tone +1F44B 1F3FC ; fully-qualified # 👋🏼 E1.0 waving hand: medium-light skin tone +1F44B 1F3FD ; fully-qualified # 👋🏽 E1.0 waving hand: medium skin tone +1F44B 1F3FE ; fully-qualified # 👋🏾 E1.0 waving hand: medium-dark skin tone +1F44B 1F3FF ; fully-qualified # 👋🏿 E1.0 waving hand: dark skin tone +1F91A ; fully-qualified # 🤚 E3.0 raised back of hand +1F91A 1F3FB ; fully-qualified # 🤚🏻 E3.0 raised back of hand: light skin tone +1F91A 1F3FC ; fully-qualified # 🤚🏼 E3.0 raised back of hand: medium-light skin tone +1F91A 1F3FD ; fully-qualified # 🤚🏽 E3.0 raised back of hand: medium skin tone +1F91A 1F3FE ; fully-qualified # 🤚🏾 E3.0 raised back of hand: medium-dark skin tone +1F91A 1F3FF ; fully-qualified # 🤚🏿 E3.0 raised back of hand: dark skin tone +1F590 FE0F ; fully-qualified # 🖐️ E0.7 hand with fingers splayed +1F590 ; unqualified # 🖐 E0.7 hand with fingers splayed +1F590 1F3FB ; fully-qualified # 🖐🏻 E1.0 hand with fingers splayed: light skin tone +1F590 1F3FC ; fully-qualified # 🖐🏼 E1.0 hand with fingers splayed: medium-light skin tone +1F590 1F3FD ; fully-qualified # 🖐🏽 E1.0 hand with fingers splayed: medium skin tone +1F590 1F3FE ; fully-qualified # 🖐🏾 E1.0 hand with fingers splayed: medium-dark skin tone +1F590 1F3FF ; fully-qualified # 🖐🏿 E1.0 hand with fingers splayed: dark skin tone +270B ; fully-qualified # ✋ E0.6 raised hand +270B 1F3FB ; fully-qualified # ✋🏻 E1.0 raised hand: light skin tone +270B 1F3FC ; fully-qualified # ✋🏼 E1.0 raised hand: medium-light skin tone +270B 1F3FD ; fully-qualified # ✋🏽 E1.0 raised hand: medium skin tone +270B 1F3FE ; fully-qualified # ✋🏾 E1.0 raised hand: medium-dark skin tone +270B 1F3FF ; fully-qualified # ✋🏿 E1.0 raised hand: dark skin tone +1F596 ; fully-qualified # 🖖 E1.0 vulcan salute +1F596 1F3FB ; fully-qualified # 🖖🏻 E1.0 vulcan salute: light skin tone +1F596 1F3FC ; fully-qualified # 🖖🏼 E1.0 vulcan salute: medium-light skin tone +1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone +1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone +1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone + +# subgroup: hand-fingers-partial +1F44C ; fully-qualified # 👌 E0.6 OK hand +1F44C 1F3FB ; fully-qualified # 👌🏻 E1.0 OK hand: light skin tone +1F44C 1F3FC ; fully-qualified # 👌🏼 E1.0 OK hand: medium-light skin tone +1F44C 1F3FD ; fully-qualified # 👌🏽 E1.0 OK hand: medium skin tone +1F44C 1F3FE ; fully-qualified # 👌🏾 E1.0 OK hand: medium-dark skin tone +1F44C 1F3FF ; fully-qualified # 👌🏿 E1.0 OK hand: dark skin tone +1F90C ; fully-qualified # 🤌 E13.0 pinched fingers +1F90C 1F3FB ; fully-qualified # 🤌🏻 E13.0 pinched fingers: light skin tone +1F90C 1F3FC ; fully-qualified # 🤌🏼 E13.0 pinched fingers: medium-light skin tone +1F90C 1F3FD ; fully-qualified # 🤌🏽 E13.0 pinched fingers: medium skin tone +1F90C 1F3FE ; fully-qualified # 🤌🏾 E13.0 pinched fingers: medium-dark skin tone +1F90C 1F3FF ; fully-qualified # 🤌🏿 E13.0 pinched fingers: dark skin tone +1F90F ; fully-qualified # 🤏 E12.0 pinching hand +1F90F 1F3FB ; fully-qualified # 🤏🏻 E12.0 pinching hand: light skin tone +1F90F 1F3FC ; fully-qualified # 🤏🏼 E12.0 pinching hand: medium-light skin tone +1F90F 1F3FD ; fully-qualified # 🤏🏽 E12.0 pinching hand: medium skin tone +1F90F 1F3FE ; fully-qualified # 🤏🏾 E12.0 pinching hand: medium-dark skin tone +1F90F 1F3FF ; fully-qualified # 🤏🏿 E12.0 pinching hand: dark skin tone +270C FE0F ; fully-qualified # ✌️ E0.6 victory hand +270C ; unqualified # ✌ E0.6 victory hand +270C 1F3FB ; fully-qualified # ✌🏻 E1.0 victory hand: light skin tone +270C 1F3FC ; fully-qualified # ✌🏼 E1.0 victory hand: medium-light skin tone +270C 1F3FD ; fully-qualified # ✌🏽 E1.0 victory hand: medium skin tone +270C 1F3FE ; fully-qualified # ✌🏾 E1.0 victory hand: medium-dark skin tone +270C 1F3FF ; fully-qualified # ✌🏿 E1.0 victory hand: dark skin tone +1F91E ; fully-qualified # 🤞 E3.0 crossed fingers +1F91E 1F3FB ; fully-qualified # 🤞🏻 E3.0 crossed fingers: light skin tone +1F91E 1F3FC ; fully-qualified # 🤞🏼 E3.0 crossed fingers: medium-light skin tone +1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone +1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone +1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone +1F91F ; fully-qualified # 🤟 E5.0 love-you gesture +1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone +1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone +1F91F 1F3FD ; fully-qualified # 🤟🏽 E5.0 love-you gesture: medium skin tone +1F91F 1F3FE ; fully-qualified # 🤟🏾 E5.0 love-you gesture: medium-dark skin tone +1F91F 1F3FF ; fully-qualified # 🤟🏿 E5.0 love-you gesture: dark skin tone +1F918 ; fully-qualified # 🤘 E1.0 sign of the horns +1F918 1F3FB ; fully-qualified # 🤘🏻 E1.0 sign of the horns: light skin tone +1F918 1F3FC ; fully-qualified # 🤘🏼 E1.0 sign of the horns: medium-light skin tone +1F918 1F3FD ; fully-qualified # 🤘🏽 E1.0 sign of the horns: medium skin tone +1F918 1F3FE ; fully-qualified # 🤘🏾 E1.0 sign of the horns: medium-dark skin tone +1F918 1F3FF ; fully-qualified # 🤘🏿 E1.0 sign of the horns: dark skin tone +1F919 ; fully-qualified # 🤙 E3.0 call me hand +1F919 1F3FB ; fully-qualified # 🤙🏻 E3.0 call me hand: light skin tone +1F919 1F3FC ; fully-qualified # 🤙🏼 E3.0 call me hand: medium-light skin tone +1F919 1F3FD ; fully-qualified # 🤙🏽 E3.0 call me hand: medium skin tone +1F919 1F3FE ; fully-qualified # 🤙🏾 E3.0 call me hand: medium-dark skin tone +1F919 1F3FF ; fully-qualified # 🤙🏿 E3.0 call me hand: dark skin tone + +# subgroup: hand-single-finger +1F448 ; fully-qualified # 👈 E0.6 backhand index pointing left +1F448 1F3FB ; fully-qualified # 👈🏻 E1.0 backhand index pointing left: light skin tone +1F448 1F3FC ; fully-qualified # 👈🏼 E1.0 backhand index pointing left: medium-light skin tone +1F448 1F3FD ; fully-qualified # 👈🏽 E1.0 backhand index pointing left: medium skin tone +1F448 1F3FE ; fully-qualified # 👈🏾 E1.0 backhand index pointing left: medium-dark skin tone +1F448 1F3FF ; fully-qualified # 👈🏿 E1.0 backhand index pointing left: dark skin tone +1F449 ; fully-qualified # 👉 E0.6 backhand index pointing right +1F449 1F3FB ; fully-qualified # 👉🏻 E1.0 backhand index pointing right: light skin tone +1F449 1F3FC ; fully-qualified # 👉🏼 E1.0 backhand index pointing right: medium-light skin tone +1F449 1F3FD ; fully-qualified # 👉🏽 E1.0 backhand index pointing right: medium skin tone +1F449 1F3FE ; fully-qualified # 👉🏾 E1.0 backhand index pointing right: medium-dark skin tone +1F449 1F3FF ; fully-qualified # 👉🏿 E1.0 backhand index pointing right: dark skin tone +1F446 ; fully-qualified # 👆 E0.6 backhand index pointing up +1F446 1F3FB ; fully-qualified # 👆🏻 E1.0 backhand index pointing up: light skin tone +1F446 1F3FC ; fully-qualified # 👆🏼 E1.0 backhand index pointing up: medium-light skin tone +1F446 1F3FD ; fully-qualified # 👆🏽 E1.0 backhand index pointing up: medium skin tone +1F446 1F3FE ; fully-qualified # 👆🏾 E1.0 backhand index pointing up: medium-dark skin tone +1F446 1F3FF ; fully-qualified # 👆🏿 E1.0 backhand index pointing up: dark skin tone +1F595 ; fully-qualified # 🖕 E1.0 middle finger +1F595 1F3FB ; fully-qualified # 🖕🏻 E1.0 middle finger: light skin tone +1F595 1F3FC ; fully-qualified # 🖕🏼 E1.0 middle finger: medium-light skin tone +1F595 1F3FD ; fully-qualified # 🖕🏽 E1.0 middle finger: medium skin tone +1F595 1F3FE ; fully-qualified # 🖕🏾 E1.0 middle finger: medium-dark skin tone +1F595 1F3FF ; fully-qualified # 🖕🏿 E1.0 middle finger: dark skin tone +1F447 ; fully-qualified # 👇 E0.6 backhand index pointing down +1F447 1F3FB ; fully-qualified # 👇🏻 E1.0 backhand index pointing down: light skin tone +1F447 1F3FC ; fully-qualified # 👇🏼 E1.0 backhand index pointing down: medium-light skin tone +1F447 1F3FD ; fully-qualified # 👇🏽 E1.0 backhand index pointing down: medium skin tone +1F447 1F3FE ; fully-qualified # 👇🏾 E1.0 backhand index pointing down: medium-dark skin tone +1F447 1F3FF ; fully-qualified # 👇🏿 E1.0 backhand index pointing down: dark skin tone +261D FE0F ; fully-qualified # ☝️ E0.6 index pointing up +261D ; unqualified # ☝ E0.6 index pointing up +261D 1F3FB ; fully-qualified # ☝🏻 E1.0 index pointing up: light skin tone +261D 1F3FC ; fully-qualified # ☝🏼 E1.0 index pointing up: medium-light skin tone +261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone +261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone +261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone + +# subgroup: hand-fingers-closed +1F44D ; fully-qualified # 👍 E0.6 thumbs up +1F44D 1F3FB ; fully-qualified # 👍🏻 E1.0 thumbs up: light skin tone +1F44D 1F3FC ; fully-qualified # 👍🏼 E1.0 thumbs up: medium-light skin tone +1F44D 1F3FD ; fully-qualified # 👍🏽 E1.0 thumbs up: medium skin tone +1F44D 1F3FE ; fully-qualified # 👍🏾 E1.0 thumbs up: medium-dark skin tone +1F44D 1F3FF ; fully-qualified # 👍🏿 E1.0 thumbs up: dark skin tone +1F44E ; fully-qualified # 👎 E0.6 thumbs down +1F44E 1F3FB ; fully-qualified # 👎🏻 E1.0 thumbs down: light skin tone +1F44E 1F3FC ; fully-qualified # 👎🏼 E1.0 thumbs down: medium-light skin tone +1F44E 1F3FD ; fully-qualified # 👎🏽 E1.0 thumbs down: medium skin tone +1F44E 1F3FE ; fully-qualified # 👎🏾 E1.0 thumbs down: medium-dark skin tone +1F44E 1F3FF ; fully-qualified # 👎🏿 E1.0 thumbs down: dark skin tone +270A ; fully-qualified # ✊ E0.6 raised fist +270A 1F3FB ; fully-qualified # ✊🏻 E1.0 raised fist: light skin tone +270A 1F3FC ; fully-qualified # ✊🏼 E1.0 raised fist: medium-light skin tone +270A 1F3FD ; fully-qualified # ✊🏽 E1.0 raised fist: medium skin tone +270A 1F3FE ; fully-qualified # ✊🏾 E1.0 raised fist: medium-dark skin tone +270A 1F3FF ; fully-qualified # ✊🏿 E1.0 raised fist: dark skin tone +1F44A ; fully-qualified # 👊 E0.6 oncoming fist +1F44A 1F3FB ; fully-qualified # 👊🏻 E1.0 oncoming fist: light skin tone +1F44A 1F3FC ; fully-qualified # 👊🏼 E1.0 oncoming fist: medium-light skin tone +1F44A 1F3FD ; fully-qualified # 👊🏽 E1.0 oncoming fist: medium skin tone +1F44A 1F3FE ; fully-qualified # 👊🏾 E1.0 oncoming fist: medium-dark skin tone +1F44A 1F3FF ; fully-qualified # 👊🏿 E1.0 oncoming fist: dark skin tone +1F91B ; fully-qualified # 🤛 E3.0 left-facing fist +1F91B 1F3FB ; fully-qualified # 🤛🏻 E3.0 left-facing fist: light skin tone +1F91B 1F3FC ; fully-qualified # 🤛🏼 E3.0 left-facing fist: medium-light skin tone +1F91B 1F3FD ; fully-qualified # 🤛🏽 E3.0 left-facing fist: medium skin tone +1F91B 1F3FE ; fully-qualified # 🤛🏾 E3.0 left-facing fist: medium-dark skin tone +1F91B 1F3FF ; fully-qualified # 🤛🏿 E3.0 left-facing fist: dark skin tone +1F91C ; fully-qualified # 🤜 E3.0 right-facing fist +1F91C 1F3FB ; fully-qualified # 🤜🏻 E3.0 right-facing fist: light skin tone +1F91C 1F3FC ; fully-qualified # 🤜🏼 E3.0 right-facing fist: medium-light skin tone +1F91C 1F3FD ; fully-qualified # 🤜🏽 E3.0 right-facing fist: medium skin tone +1F91C 1F3FE ; fully-qualified # 🤜🏾 E3.0 right-facing fist: medium-dark skin tone +1F91C 1F3FF ; fully-qualified # 🤜🏿 E3.0 right-facing fist: dark skin tone + +# subgroup: hands +1F44F ; fully-qualified # 👏 E0.6 clapping hands +1F44F 1F3FB ; fully-qualified # 👏🏻 E1.0 clapping hands: light skin tone +1F44F 1F3FC ; fully-qualified # 👏🏼 E1.0 clapping hands: medium-light skin tone +1F44F 1F3FD ; fully-qualified # 👏🏽 E1.0 clapping hands: medium skin tone +1F44F 1F3FE ; fully-qualified # 👏🏾 E1.0 clapping hands: medium-dark skin tone +1F44F 1F3FF ; fully-qualified # 👏🏿 E1.0 clapping hands: dark skin tone +1F64C ; fully-qualified # 🙌 E0.6 raising hands +1F64C 1F3FB ; fully-qualified # 🙌🏻 E1.0 raising hands: light skin tone +1F64C 1F3FC ; fully-qualified # 🙌🏼 E1.0 raising hands: medium-light skin tone +1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone +1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone +1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone +1F450 ; fully-qualified # 👐 E0.6 open hands +1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone +1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone +1F450 1F3FD ; fully-qualified # 👐🏽 E1.0 open hands: medium skin tone +1F450 1F3FE ; fully-qualified # 👐🏾 E1.0 open hands: medium-dark skin tone +1F450 1F3FF ; fully-qualified # 👐🏿 E1.0 open hands: dark skin tone +1F932 ; fully-qualified # 🤲 E5.0 palms up together +1F932 1F3FB ; fully-qualified # 🤲🏻 E5.0 palms up together: light skin tone +1F932 1F3FC ; fully-qualified # 🤲🏼 E5.0 palms up together: medium-light skin tone +1F932 1F3FD ; fully-qualified # 🤲🏽 E5.0 palms up together: medium skin tone +1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone +1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone +1F91D ; fully-qualified # 🤝 E3.0 handshake +1F64F ; fully-qualified # 🙏 E0.6 folded hands +1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone +1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone +1F64F 1F3FD ; fully-qualified # 🙏🏽 E1.0 folded hands: medium skin tone +1F64F 1F3FE ; fully-qualified # 🙏🏾 E1.0 folded hands: medium-dark skin tone +1F64F 1F3FF ; fully-qualified # 🙏🏿 E1.0 folded hands: dark skin tone + +# subgroup: hand-prop +270D FE0F ; fully-qualified # ✍️ E0.7 writing hand +270D ; unqualified # ✍ E0.7 writing hand +270D 1F3FB ; fully-qualified # ✍🏻 E1.0 writing hand: light skin tone +270D 1F3FC ; fully-qualified # ✍🏼 E1.0 writing hand: medium-light skin tone +270D 1F3FD ; fully-qualified # ✍🏽 E1.0 writing hand: medium skin tone +270D 1F3FE ; fully-qualified # ✍🏾 E1.0 writing hand: medium-dark skin tone +270D 1F3FF ; fully-qualified # ✍🏿 E1.0 writing hand: dark skin tone +1F485 ; fully-qualified # 💅 E0.6 nail polish +1F485 1F3FB ; fully-qualified # 💅🏻 E1.0 nail polish: light skin tone +1F485 1F3FC ; fully-qualified # 💅🏼 E1.0 nail polish: medium-light skin tone +1F485 1F3FD ; fully-qualified # 💅🏽 E1.0 nail polish: medium skin tone +1F485 1F3FE ; fully-qualified # 💅🏾 E1.0 nail polish: medium-dark skin tone +1F485 1F3FF ; fully-qualified # 💅🏿 E1.0 nail polish: dark skin tone +1F933 ; fully-qualified # 🤳 E3.0 selfie +1F933 1F3FB ; fully-qualified # 🤳🏻 E3.0 selfie: light skin tone +1F933 1F3FC ; fully-qualified # 🤳🏼 E3.0 selfie: medium-light skin tone +1F933 1F3FD ; fully-qualified # 🤳🏽 E3.0 selfie: medium skin tone +1F933 1F3FE ; fully-qualified # 🤳🏾 E3.0 selfie: medium-dark skin tone +1F933 1F3FF ; fully-qualified # 🤳🏿 E3.0 selfie: dark skin tone + +# subgroup: body-parts +1F4AA ; fully-qualified # 💪 E0.6 flexed biceps +1F4AA 1F3FB ; fully-qualified # 💪🏻 E1.0 flexed biceps: light skin tone +1F4AA 1F3FC ; fully-qualified # 💪🏼 E1.0 flexed biceps: medium-light skin tone +1F4AA 1F3FD ; fully-qualified # 💪🏽 E1.0 flexed biceps: medium skin tone +1F4AA 1F3FE ; fully-qualified # 💪🏾 E1.0 flexed biceps: medium-dark skin tone +1F4AA 1F3FF ; fully-qualified # 💪🏿 E1.0 flexed biceps: dark skin tone +1F9BE ; fully-qualified # 🦾 E12.0 mechanical arm +1F9BF ; fully-qualified # 🦿 E12.0 mechanical leg +1F9B5 ; fully-qualified # 🦵 E11.0 leg +1F9B5 1F3FB ; fully-qualified # 🦵🏻 E11.0 leg: light skin tone +1F9B5 1F3FC ; fully-qualified # 🦵🏼 E11.0 leg: medium-light skin tone +1F9B5 1F3FD ; fully-qualified # 🦵🏽 E11.0 leg: medium skin tone +1F9B5 1F3FE ; fully-qualified # 🦵🏾 E11.0 leg: medium-dark skin tone +1F9B5 1F3FF ; fully-qualified # 🦵🏿 E11.0 leg: dark skin tone +1F9B6 ; fully-qualified # 🦶 E11.0 foot +1F9B6 1F3FB ; fully-qualified # 🦶🏻 E11.0 foot: light skin tone +1F9B6 1F3FC ; fully-qualified # 🦶🏼 E11.0 foot: medium-light skin tone +1F9B6 1F3FD ; fully-qualified # 🦶🏽 E11.0 foot: medium skin tone +1F9B6 1F3FE ; fully-qualified # 🦶🏾 E11.0 foot: medium-dark skin tone +1F9B6 1F3FF ; fully-qualified # 🦶🏿 E11.0 foot: dark skin tone +1F442 ; fully-qualified # 👂 E0.6 ear +1F442 1F3FB ; fully-qualified # 👂🏻 E1.0 ear: light skin tone +1F442 1F3FC ; fully-qualified # 👂🏼 E1.0 ear: medium-light skin tone +1F442 1F3FD ; fully-qualified # 👂🏽 E1.0 ear: medium skin tone +1F442 1F3FE ; fully-qualified # 👂🏾 E1.0 ear: medium-dark skin tone +1F442 1F3FF ; fully-qualified # 👂🏿 E1.0 ear: dark skin tone +1F9BB ; fully-qualified # 🦻 E12.0 ear with hearing aid +1F9BB 1F3FB ; fully-qualified # 🦻🏻 E12.0 ear with hearing aid: light skin tone +1F9BB 1F3FC ; fully-qualified # 🦻🏼 E12.0 ear with hearing aid: medium-light skin tone +1F9BB 1F3FD ; fully-qualified # 🦻🏽 E12.0 ear with hearing aid: medium skin tone +1F9BB 1F3FE ; fully-qualified # 🦻🏾 E12.0 ear with hearing aid: medium-dark skin tone +1F9BB 1F3FF ; fully-qualified # 🦻🏿 E12.0 ear with hearing aid: dark skin tone +1F443 ; fully-qualified # 👃 E0.6 nose +1F443 1F3FB ; fully-qualified # 👃🏻 E1.0 nose: light skin tone +1F443 1F3FC ; fully-qualified # 👃🏼 E1.0 nose: medium-light skin tone +1F443 1F3FD ; fully-qualified # 👃🏽 E1.0 nose: medium skin tone +1F443 1F3FE ; fully-qualified # 👃🏾 E1.0 nose: medium-dark skin tone +1F443 1F3FF ; fully-qualified # 👃🏿 E1.0 nose: dark skin tone +1F9E0 ; fully-qualified # 🧠 E5.0 brain +1FAC0 ; fully-qualified # 🫀 E13.0 anatomical heart +1FAC1 ; fully-qualified # 🫁 E13.0 lungs +1F9B7 ; fully-qualified # 🦷 E11.0 tooth +1F9B4 ; fully-qualified # 🦴 E11.0 bone +1F440 ; fully-qualified # 👀 E0.6 eyes +1F441 FE0F ; fully-qualified # 👁️ E0.7 eye +1F441 ; unqualified # 👁 E0.7 eye +1F445 ; fully-qualified # 👅 E0.6 tongue +1F444 ; fully-qualified # 👄 E0.6 mouth + +# subgroup: person +1F476 ; fully-qualified # 👶 E0.6 baby +1F476 1F3FB ; fully-qualified # 👶🏻 E1.0 baby: light skin tone +1F476 1F3FC ; fully-qualified # 👶🏼 E1.0 baby: medium-light skin tone +1F476 1F3FD ; fully-qualified # 👶🏽 E1.0 baby: medium skin tone +1F476 1F3FE ; fully-qualified # 👶🏾 E1.0 baby: medium-dark skin tone +1F476 1F3FF ; fully-qualified # 👶🏿 E1.0 baby: dark skin tone +1F9D2 ; fully-qualified # 🧒 E5.0 child +1F9D2 1F3FB ; fully-qualified # 🧒🏻 E5.0 child: light skin tone +1F9D2 1F3FC ; fully-qualified # 🧒🏼 E5.0 child: medium-light skin tone +1F9D2 1F3FD ; fully-qualified # 🧒🏽 E5.0 child: medium skin tone +1F9D2 1F3FE ; fully-qualified # 🧒🏾 E5.0 child: medium-dark skin tone +1F9D2 1F3FF ; fully-qualified # 🧒🏿 E5.0 child: dark skin tone +1F466 ; fully-qualified # 👦 E0.6 boy +1F466 1F3FB ; fully-qualified # 👦🏻 E1.0 boy: light skin tone +1F466 1F3FC ; fully-qualified # 👦🏼 E1.0 boy: medium-light skin tone +1F466 1F3FD ; fully-qualified # 👦🏽 E1.0 boy: medium skin tone +1F466 1F3FE ; fully-qualified # 👦🏾 E1.0 boy: medium-dark skin tone +1F466 1F3FF ; fully-qualified # 👦🏿 E1.0 boy: dark skin tone +1F467 ; fully-qualified # 👧 E0.6 girl +1F467 1F3FB ; fully-qualified # 👧🏻 E1.0 girl: light skin tone +1F467 1F3FC ; fully-qualified # 👧🏼 E1.0 girl: medium-light skin tone +1F467 1F3FD ; fully-qualified # 👧🏽 E1.0 girl: medium skin tone +1F467 1F3FE ; fully-qualified # 👧🏾 E1.0 girl: medium-dark skin tone +1F467 1F3FF ; fully-qualified # 👧🏿 E1.0 girl: dark skin tone +1F9D1 ; fully-qualified # 🧑 E5.0 person +1F9D1 1F3FB ; fully-qualified # 🧑🏻 E5.0 person: light skin tone +1F9D1 1F3FC ; fully-qualified # 🧑🏼 E5.0 person: medium-light skin tone +1F9D1 1F3FD ; fully-qualified # 🧑🏽 E5.0 person: medium skin tone +1F9D1 1F3FE ; fully-qualified # 🧑🏾 E5.0 person: medium-dark skin tone +1F9D1 1F3FF ; fully-qualified # 🧑🏿 E5.0 person: dark skin tone +1F471 ; fully-qualified # 👱 E0.6 person: blond hair +1F471 1F3FB ; fully-qualified # 👱🏻 E1.0 person: light skin tone, blond hair +1F471 1F3FC ; fully-qualified # 👱🏼 E1.0 person: medium-light skin tone, blond hair +1F471 1F3FD ; fully-qualified # 👱🏽 E1.0 person: medium skin tone, blond hair +1F471 1F3FE ; fully-qualified # 👱🏾 E1.0 person: medium-dark skin tone, blond hair +1F471 1F3FF ; fully-qualified # 👱🏿 E1.0 person: dark skin tone, blond hair +1F468 ; fully-qualified # 👨 E0.6 man +1F468 1F3FB ; fully-qualified # 👨🏻 E1.0 man: light skin tone +1F468 1F3FC ; fully-qualified # 👨🏼 E1.0 man: medium-light skin tone +1F468 1F3FD ; fully-qualified # 👨🏽 E1.0 man: medium skin tone +1F468 1F3FE ; fully-qualified # 👨🏾 E1.0 man: medium-dark skin tone +1F468 1F3FF ; fully-qualified # 👨🏿 E1.0 man: dark skin tone +1F9D4 ; fully-qualified # 🧔 E5.0 man: beard +1F9D4 1F3FB ; fully-qualified # 🧔🏻 E5.0 man: light skin tone, beard +1F9D4 1F3FC ; fully-qualified # 🧔🏼 E5.0 man: medium-light skin tone, beard +1F9D4 1F3FD ; fully-qualified # 🧔🏽 E5.0 man: medium skin tone, beard +1F9D4 1F3FE ; fully-qualified # 🧔🏾 E5.0 man: medium-dark skin tone, beard +1F9D4 1F3FF ; fully-qualified # 🧔🏿 E5.0 man: dark skin tone, beard +1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 E11.0 man: red hair +1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 E11.0 man: light skin tone, red hair +1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 E11.0 man: medium-light skin tone, red hair +1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 E11.0 man: medium skin tone, red hair +1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 E11.0 man: medium-dark skin tone, red hair +1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 E11.0 man: dark skin tone, red hair +1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 E11.0 man: curly hair +1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 E11.0 man: light skin tone, curly hair +1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 E11.0 man: medium-light skin tone, curly hair +1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 E11.0 man: medium skin tone, curly hair +1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 E11.0 man: medium-dark skin tone, curly hair +1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 E11.0 man: dark skin tone, curly hair +1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 E11.0 man: white hair +1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 E11.0 man: light skin tone, white hair +1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 E11.0 man: medium-light skin tone, white hair +1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 E11.0 man: medium skin tone, white hair +1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 E11.0 man: medium-dark skin tone, white hair +1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 E11.0 man: dark skin tone, white hair +1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 E11.0 man: bald +1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 E11.0 man: light skin tone, bald +1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 E11.0 man: medium-light skin tone, bald +1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 E11.0 man: medium skin tone, bald +1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 E11.0 man: medium-dark skin tone, bald +1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 E11.0 man: dark skin tone, bald +1F469 ; fully-qualified # 👩 E0.6 woman +1F469 1F3FB ; fully-qualified # 👩🏻 E1.0 woman: light skin tone +1F469 1F3FC ; fully-qualified # 👩🏼 E1.0 woman: medium-light skin tone +1F469 1F3FD ; fully-qualified # 👩🏽 E1.0 woman: medium skin tone +1F469 1F3FE ; fully-qualified # 👩🏾 E1.0 woman: medium-dark skin tone +1F469 1F3FF ; fully-qualified # 👩🏿 E1.0 woman: dark skin tone +1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 E11.0 woman: red hair +1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 E11.0 woman: light skin tone, red hair +1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 E11.0 woman: medium-light skin tone, red hair +1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 E11.0 woman: medium skin tone, red hair +1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 E11.0 woman: medium-dark skin tone, red hair +1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 E11.0 woman: dark skin tone, red hair +1F9D1 200D 1F9B0 ; fully-qualified # 🧑‍🦰 E12.1 person: red hair +1F9D1 1F3FB 200D 1F9B0 ; fully-qualified # 🧑🏻‍🦰 E12.1 person: light skin tone, red hair +1F9D1 1F3FC 200D 1F9B0 ; fully-qualified # 🧑🏼‍🦰 E12.1 person: medium-light skin tone, red hair +1F9D1 1F3FD 200D 1F9B0 ; fully-qualified # 🧑🏽‍🦰 E12.1 person: medium skin tone, red hair +1F9D1 1F3FE 200D 1F9B0 ; fully-qualified # 🧑🏾‍🦰 E12.1 person: medium-dark skin tone, red hair +1F9D1 1F3FF 200D 1F9B0 ; fully-qualified # 🧑🏿‍🦰 E12.1 person: dark skin tone, red hair +1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 E11.0 woman: curly hair +1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 E11.0 woman: light skin tone, curly hair +1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 E11.0 woman: medium-light skin tone, curly hair +1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 E11.0 woman: medium skin tone, curly hair +1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 E11.0 woman: medium-dark skin tone, curly hair +1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 E11.0 woman: dark skin tone, curly hair +1F9D1 200D 1F9B1 ; fully-qualified # 🧑‍🦱 E12.1 person: curly hair +1F9D1 1F3FB 200D 1F9B1 ; fully-qualified # 🧑🏻‍🦱 E12.1 person: light skin tone, curly hair +1F9D1 1F3FC 200D 1F9B1 ; fully-qualified # 🧑🏼‍🦱 E12.1 person: medium-light skin tone, curly hair +1F9D1 1F3FD 200D 1F9B1 ; fully-qualified # 🧑🏽‍🦱 E12.1 person: medium skin tone, curly hair +1F9D1 1F3FE 200D 1F9B1 ; fully-qualified # 🧑🏾‍🦱 E12.1 person: medium-dark skin tone, curly hair +1F9D1 1F3FF 200D 1F9B1 ; fully-qualified # 🧑🏿‍🦱 E12.1 person: dark skin tone, curly hair +1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 E11.0 woman: white hair +1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 E11.0 woman: light skin tone, white hair +1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 E11.0 woman: medium-light skin tone, white hair +1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 E11.0 woman: medium skin tone, white hair +1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 E11.0 woman: medium-dark skin tone, white hair +1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 E11.0 woman: dark skin tone, white hair +1F9D1 200D 1F9B3 ; fully-qualified # 🧑‍🦳 E12.1 person: white hair +1F9D1 1F3FB 200D 1F9B3 ; fully-qualified # 🧑🏻‍🦳 E12.1 person: light skin tone, white hair +1F9D1 1F3FC 200D 1F9B3 ; fully-qualified # 🧑🏼‍🦳 E12.1 person: medium-light skin tone, white hair +1F9D1 1F3FD 200D 1F9B3 ; fully-qualified # 🧑🏽‍🦳 E12.1 person: medium skin tone, white hair +1F9D1 1F3FE 200D 1F9B3 ; fully-qualified # 🧑🏾‍🦳 E12.1 person: medium-dark skin tone, white hair +1F9D1 1F3FF 200D 1F9B3 ; fully-qualified # 🧑🏿‍🦳 E12.1 person: dark skin tone, white hair +1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 E11.0 woman: bald +1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 E11.0 woman: light skin tone, bald +1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 E11.0 woman: medium-light skin tone, bald +1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 E11.0 woman: medium skin tone, bald +1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 E11.0 woman: medium-dark skin tone, bald +1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 E11.0 woman: dark skin tone, bald +1F9D1 200D 1F9B2 ; fully-qualified # 🧑‍🦲 E12.1 person: bald +1F9D1 1F3FB 200D 1F9B2 ; fully-qualified # 🧑🏻‍🦲 E12.1 person: light skin tone, bald +1F9D1 1F3FC 200D 1F9B2 ; fully-qualified # 🧑🏼‍🦲 E12.1 person: medium-light skin tone, bald +1F9D1 1F3FD 200D 1F9B2 ; fully-qualified # 🧑🏽‍🦲 E12.1 person: medium skin tone, bald +1F9D1 1F3FE 200D 1F9B2 ; fully-qualified # 🧑🏾‍🦲 E12.1 person: medium-dark skin tone, bald +1F9D1 1F3FF 200D 1F9B2 ; fully-qualified # 🧑🏿‍🦲 E12.1 person: dark skin tone, bald +1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ E4.0 woman: blond hair +1F471 200D 2640 ; minimally-qualified # 👱‍♀ E4.0 woman: blond hair +1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ E4.0 woman: light skin tone, blond hair +1F471 1F3FB 200D 2640 ; minimally-qualified # 👱🏻‍♀ E4.0 woman: light skin tone, blond hair +1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ E4.0 woman: medium-light skin tone, blond hair +1F471 1F3FC 200D 2640 ; minimally-qualified # 👱🏼‍♀ E4.0 woman: medium-light skin tone, blond hair +1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ E4.0 woman: medium skin tone, blond hair +1F471 1F3FD 200D 2640 ; minimally-qualified # 👱🏽‍♀ E4.0 woman: medium skin tone, blond hair +1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ E4.0 woman: medium-dark skin tone, blond hair +1F471 1F3FE 200D 2640 ; minimally-qualified # 👱🏾‍♀ E4.0 woman: medium-dark skin tone, blond hair +1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ E4.0 woman: dark skin tone, blond hair +1F471 1F3FF 200D 2640 ; minimally-qualified # 👱🏿‍♀ E4.0 woman: dark skin tone, blond hair +1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ E4.0 man: blond hair +1F471 200D 2642 ; minimally-qualified # 👱‍♂ E4.0 man: blond hair +1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ E4.0 man: light skin tone, blond hair +1F471 1F3FB 200D 2642 ; minimally-qualified # 👱🏻‍♂ E4.0 man: light skin tone, blond hair +1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ E4.0 man: medium-light skin tone, blond hair +1F471 1F3FC 200D 2642 ; minimally-qualified # 👱🏼‍♂ E4.0 man: medium-light skin tone, blond hair +1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ E4.0 man: medium skin tone, blond hair +1F471 1F3FD 200D 2642 ; minimally-qualified # 👱🏽‍♂ E4.0 man: medium skin tone, blond hair +1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ E4.0 man: medium-dark skin tone, blond hair +1F471 1F3FE 200D 2642 ; minimally-qualified # 👱🏾‍♂ E4.0 man: medium-dark skin tone, blond hair +1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ E4.0 man: dark skin tone, blond hair +1F471 1F3FF 200D 2642 ; minimally-qualified # 👱🏿‍♂ E4.0 man: dark skin tone, blond hair +1F9D3 ; fully-qualified # 🧓 E5.0 older person +1F9D3 1F3FB ; fully-qualified # 🧓🏻 E5.0 older person: light skin tone +1F9D3 1F3FC ; fully-qualified # 🧓🏼 E5.0 older person: medium-light skin tone +1F9D3 1F3FD ; fully-qualified # 🧓🏽 E5.0 older person: medium skin tone +1F9D3 1F3FE ; fully-qualified # 🧓🏾 E5.0 older person: medium-dark skin tone +1F9D3 1F3FF ; fully-qualified # 🧓🏿 E5.0 older person: dark skin tone +1F474 ; fully-qualified # 👴 E0.6 old man +1F474 1F3FB ; fully-qualified # 👴🏻 E1.0 old man: light skin tone +1F474 1F3FC ; fully-qualified # 👴🏼 E1.0 old man: medium-light skin tone +1F474 1F3FD ; fully-qualified # 👴🏽 E1.0 old man: medium skin tone +1F474 1F3FE ; fully-qualified # 👴🏾 E1.0 old man: medium-dark skin tone +1F474 1F3FF ; fully-qualified # 👴🏿 E1.0 old man: dark skin tone +1F475 ; fully-qualified # 👵 E0.6 old woman +1F475 1F3FB ; fully-qualified # 👵🏻 E1.0 old woman: light skin tone +1F475 1F3FC ; fully-qualified # 👵🏼 E1.0 old woman: medium-light skin tone +1F475 1F3FD ; fully-qualified # 👵🏽 E1.0 old woman: medium skin tone +1F475 1F3FE ; fully-qualified # 👵🏾 E1.0 old woman: medium-dark skin tone +1F475 1F3FF ; fully-qualified # 👵🏿 E1.0 old woman: dark skin tone + +# subgroup: person-gesture +1F64D ; fully-qualified # 🙍 E0.6 person frowning +1F64D 1F3FB ; fully-qualified # 🙍🏻 E1.0 person frowning: light skin tone +1F64D 1F3FC ; fully-qualified # 🙍🏼 E1.0 person frowning: medium-light skin tone +1F64D 1F3FD ; fully-qualified # 🙍🏽 E1.0 person frowning: medium skin tone +1F64D 1F3FE ; fully-qualified # 🙍🏾 E1.0 person frowning: medium-dark skin tone +1F64D 1F3FF ; fully-qualified # 🙍🏿 E1.0 person frowning: dark skin tone +1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ E4.0 man frowning +1F64D 200D 2642 ; minimally-qualified # 🙍‍♂ E4.0 man frowning +1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ E4.0 man frowning: light skin tone +1F64D 1F3FB 200D 2642 ; minimally-qualified # 🙍🏻‍♂ E4.0 man frowning: light skin tone +1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ E4.0 man frowning: medium-light skin tone +1F64D 1F3FC 200D 2642 ; minimally-qualified # 🙍🏼‍♂ E4.0 man frowning: medium-light skin tone +1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ E4.0 man frowning: medium skin tone +1F64D 1F3FD 200D 2642 ; minimally-qualified # 🙍🏽‍♂ E4.0 man frowning: medium skin tone +1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ E4.0 man frowning: medium-dark skin tone +1F64D 1F3FE 200D 2642 ; minimally-qualified # 🙍🏾‍♂ E4.0 man frowning: medium-dark skin tone +1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ E4.0 man frowning: dark skin tone +1F64D 1F3FF 200D 2642 ; minimally-qualified # 🙍🏿‍♂ E4.0 man frowning: dark skin tone +1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ E4.0 woman frowning +1F64D 200D 2640 ; minimally-qualified # 🙍‍♀ E4.0 woman frowning +1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ E4.0 woman frowning: light skin tone +1F64D 1F3FB 200D 2640 ; minimally-qualified # 🙍🏻‍♀ E4.0 woman frowning: light skin tone +1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ E4.0 woman frowning: medium-light skin tone +1F64D 1F3FC 200D 2640 ; minimally-qualified # 🙍🏼‍♀ E4.0 woman frowning: medium-light skin tone +1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ E4.0 woman frowning: medium skin tone +1F64D 1F3FD 200D 2640 ; minimally-qualified # 🙍🏽‍♀ E4.0 woman frowning: medium skin tone +1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ E4.0 woman frowning: medium-dark skin tone +1F64D 1F3FE 200D 2640 ; minimally-qualified # 🙍🏾‍♀ E4.0 woman frowning: medium-dark skin tone +1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ E4.0 woman frowning: dark skin tone +1F64D 1F3FF 200D 2640 ; minimally-qualified # 🙍🏿‍♀ E4.0 woman frowning: dark skin tone +1F64E ; fully-qualified # 🙎 E0.6 person pouting +1F64E 1F3FB ; fully-qualified # 🙎🏻 E1.0 person pouting: light skin tone +1F64E 1F3FC ; fully-qualified # 🙎🏼 E1.0 person pouting: medium-light skin tone +1F64E 1F3FD ; fully-qualified # 🙎🏽 E1.0 person pouting: medium skin tone +1F64E 1F3FE ; fully-qualified # 🙎🏾 E1.0 person pouting: medium-dark skin tone +1F64E 1F3FF ; fully-qualified # 🙎🏿 E1.0 person pouting: dark skin tone +1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ E4.0 man pouting +1F64E 200D 2642 ; minimally-qualified # 🙎‍♂ E4.0 man pouting +1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ E4.0 man pouting: light skin tone +1F64E 1F3FB 200D 2642 ; minimally-qualified # 🙎🏻‍♂ E4.0 man pouting: light skin tone +1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ E4.0 man pouting: medium-light skin tone +1F64E 1F3FC 200D 2642 ; minimally-qualified # 🙎🏼‍♂ E4.0 man pouting: medium-light skin tone +1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ E4.0 man pouting: medium skin tone +1F64E 1F3FD 200D 2642 ; minimally-qualified # 🙎🏽‍♂ E4.0 man pouting: medium skin tone +1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ E4.0 man pouting: medium-dark skin tone +1F64E 1F3FE 200D 2642 ; minimally-qualified # 🙎🏾‍♂ E4.0 man pouting: medium-dark skin tone +1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ E4.0 man pouting: dark skin tone +1F64E 1F3FF 200D 2642 ; minimally-qualified # 🙎🏿‍♂ E4.0 man pouting: dark skin tone +1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ E4.0 woman pouting +1F64E 200D 2640 ; minimally-qualified # 🙎‍♀ E4.0 woman pouting +1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ E4.0 woman pouting: light skin tone +1F64E 1F3FB 200D 2640 ; minimally-qualified # 🙎🏻‍♀ E4.0 woman pouting: light skin tone +1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ E4.0 woman pouting: medium-light skin tone +1F64E 1F3FC 200D 2640 ; minimally-qualified # 🙎🏼‍♀ E4.0 woman pouting: medium-light skin tone +1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ E4.0 woman pouting: medium skin tone +1F64E 1F3FD 200D 2640 ; minimally-qualified # 🙎🏽‍♀ E4.0 woman pouting: medium skin tone +1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ E4.0 woman pouting: medium-dark skin tone +1F64E 1F3FE 200D 2640 ; minimally-qualified # 🙎🏾‍♀ E4.0 woman pouting: medium-dark skin tone +1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ E4.0 woman pouting: dark skin tone +1F64E 1F3FF 200D 2640 ; minimally-qualified # 🙎🏿‍♀ E4.0 woman pouting: dark skin tone +1F645 ; fully-qualified # 🙅 E0.6 person gesturing NO +1F645 1F3FB ; fully-qualified # 🙅🏻 E1.0 person gesturing NO: light skin tone +1F645 1F3FC ; fully-qualified # 🙅🏼 E1.0 person gesturing NO: medium-light skin tone +1F645 1F3FD ; fully-qualified # 🙅🏽 E1.0 person gesturing NO: medium skin tone +1F645 1F3FE ; fully-qualified # 🙅🏾 E1.0 person gesturing NO: medium-dark skin tone +1F645 1F3FF ; fully-qualified # 🙅🏿 E1.0 person gesturing NO: dark skin tone +1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ E4.0 man gesturing NO +1F645 200D 2642 ; minimally-qualified # 🙅‍♂ E4.0 man gesturing NO +1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ E4.0 man gesturing NO: light skin tone +1F645 1F3FB 200D 2642 ; minimally-qualified # 🙅🏻‍♂ E4.0 man gesturing NO: light skin tone +1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ E4.0 man gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2642 ; minimally-qualified # 🙅🏼‍♂ E4.0 man gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ E4.0 man gesturing NO: medium skin tone +1F645 1F3FD 200D 2642 ; minimally-qualified # 🙅🏽‍♂ E4.0 man gesturing NO: medium skin tone +1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ E4.0 man gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2642 ; minimally-qualified # 🙅🏾‍♂ E4.0 man gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ E4.0 man gesturing NO: dark skin tone +1F645 1F3FF 200D 2642 ; minimally-qualified # 🙅🏿‍♂ E4.0 man gesturing NO: dark skin tone +1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ E4.0 woman gesturing NO +1F645 200D 2640 ; minimally-qualified # 🙅‍♀ E4.0 woman gesturing NO +1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ E4.0 woman gesturing NO: light skin tone +1F645 1F3FB 200D 2640 ; minimally-qualified # 🙅🏻‍♀ E4.0 woman gesturing NO: light skin tone +1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ E4.0 woman gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2640 ; minimally-qualified # 🙅🏼‍♀ E4.0 woman gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ E4.0 woman gesturing NO: medium skin tone +1F645 1F3FD 200D 2640 ; minimally-qualified # 🙅🏽‍♀ E4.0 woman gesturing NO: medium skin tone +1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ E4.0 woman gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2640 ; minimally-qualified # 🙅🏾‍♀ E4.0 woman gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ E4.0 woman gesturing NO: dark skin tone +1F645 1F3FF 200D 2640 ; minimally-qualified # 🙅🏿‍♀ E4.0 woman gesturing NO: dark skin tone +1F646 ; fully-qualified # 🙆 E0.6 person gesturing OK +1F646 1F3FB ; fully-qualified # 🙆🏻 E1.0 person gesturing OK: light skin tone +1F646 1F3FC ; fully-qualified # 🙆🏼 E1.0 person gesturing OK: medium-light skin tone +1F646 1F3FD ; fully-qualified # 🙆🏽 E1.0 person gesturing OK: medium skin tone +1F646 1F3FE ; fully-qualified # 🙆🏾 E1.0 person gesturing OK: medium-dark skin tone +1F646 1F3FF ; fully-qualified # 🙆🏿 E1.0 person gesturing OK: dark skin tone +1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ E4.0 man gesturing OK +1F646 200D 2642 ; minimally-qualified # 🙆‍♂ E4.0 man gesturing OK +1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ E4.0 man gesturing OK: light skin tone +1F646 1F3FB 200D 2642 ; minimally-qualified # 🙆🏻‍♂ E4.0 man gesturing OK: light skin tone +1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ E4.0 man gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2642 ; minimally-qualified # 🙆🏼‍♂ E4.0 man gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ E4.0 man gesturing OK: medium skin tone +1F646 1F3FD 200D 2642 ; minimally-qualified # 🙆🏽‍♂ E4.0 man gesturing OK: medium skin tone +1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ E4.0 man gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2642 ; minimally-qualified # 🙆🏾‍♂ E4.0 man gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ E4.0 man gesturing OK: dark skin tone +1F646 1F3FF 200D 2642 ; minimally-qualified # 🙆🏿‍♂ E4.0 man gesturing OK: dark skin tone +1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ E4.0 woman gesturing OK +1F646 200D 2640 ; minimally-qualified # 🙆‍♀ E4.0 woman gesturing OK +1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ E4.0 woman gesturing OK: light skin tone +1F646 1F3FB 200D 2640 ; minimally-qualified # 🙆🏻‍♀ E4.0 woman gesturing OK: light skin tone +1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ E4.0 woman gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2640 ; minimally-qualified # 🙆🏼‍♀ E4.0 woman gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ E4.0 woman gesturing OK: medium skin tone +1F646 1F3FD 200D 2640 ; minimally-qualified # 🙆🏽‍♀ E4.0 woman gesturing OK: medium skin tone +1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ E4.0 woman gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2640 ; minimally-qualified # 🙆🏾‍♀ E4.0 woman gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ E4.0 woman gesturing OK: dark skin tone +1F646 1F3FF 200D 2640 ; minimally-qualified # 🙆🏿‍♀ E4.0 woman gesturing OK: dark skin tone +1F481 ; fully-qualified # 💁 E0.6 person tipping hand +1F481 1F3FB ; fully-qualified # 💁🏻 E1.0 person tipping hand: light skin tone +1F481 1F3FC ; fully-qualified # 💁🏼 E1.0 person tipping hand: medium-light skin tone +1F481 1F3FD ; fully-qualified # 💁🏽 E1.0 person tipping hand: medium skin tone +1F481 1F3FE ; fully-qualified # 💁🏾 E1.0 person tipping hand: medium-dark skin tone +1F481 1F3FF ; fully-qualified # 💁🏿 E1.0 person tipping hand: dark skin tone +1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ E4.0 man tipping hand +1F481 200D 2642 ; minimally-qualified # 💁‍♂ E4.0 man tipping hand +1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ E4.0 man tipping hand: light skin tone +1F481 1F3FB 200D 2642 ; minimally-qualified # 💁🏻‍♂ E4.0 man tipping hand: light skin tone +1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ E4.0 man tipping hand: medium-light skin tone +1F481 1F3FC 200D 2642 ; minimally-qualified # 💁🏼‍♂ E4.0 man tipping hand: medium-light skin tone +1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ E4.0 man tipping hand: medium skin tone +1F481 1F3FD 200D 2642 ; minimally-qualified # 💁🏽‍♂ E4.0 man tipping hand: medium skin tone +1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ E4.0 man tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2642 ; minimally-qualified # 💁🏾‍♂ E4.0 man tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ E4.0 man tipping hand: dark skin tone +1F481 1F3FF 200D 2642 ; minimally-qualified # 💁🏿‍♂ E4.0 man tipping hand: dark skin tone +1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ E4.0 woman tipping hand +1F481 200D 2640 ; minimally-qualified # 💁‍♀ E4.0 woman tipping hand +1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ E4.0 woman tipping hand: light skin tone +1F481 1F3FB 200D 2640 ; minimally-qualified # 💁🏻‍♀ E4.0 woman tipping hand: light skin tone +1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ E4.0 woman tipping hand: medium-light skin tone +1F481 1F3FC 200D 2640 ; minimally-qualified # 💁🏼‍♀ E4.0 woman tipping hand: medium-light skin tone +1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ E4.0 woman tipping hand: medium skin tone +1F481 1F3FD 200D 2640 ; minimally-qualified # 💁🏽‍♀ E4.0 woman tipping hand: medium skin tone +1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ E4.0 woman tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2640 ; minimally-qualified # 💁🏾‍♀ E4.0 woman tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ E4.0 woman tipping hand: dark skin tone +1F481 1F3FF 200D 2640 ; minimally-qualified # 💁🏿‍♀ E4.0 woman tipping hand: dark skin tone +1F64B ; fully-qualified # 🙋 E0.6 person raising hand +1F64B 1F3FB ; fully-qualified # 🙋🏻 E1.0 person raising hand: light skin tone +1F64B 1F3FC ; fully-qualified # 🙋🏼 E1.0 person raising hand: medium-light skin tone +1F64B 1F3FD ; fully-qualified # 🙋🏽 E1.0 person raising hand: medium skin tone +1F64B 1F3FE ; fully-qualified # 🙋🏾 E1.0 person raising hand: medium-dark skin tone +1F64B 1F3FF ; fully-qualified # 🙋🏿 E1.0 person raising hand: dark skin tone +1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ E4.0 man raising hand +1F64B 200D 2642 ; minimally-qualified # 🙋‍♂ E4.0 man raising hand +1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ E4.0 man raising hand: light skin tone +1F64B 1F3FB 200D 2642 ; minimally-qualified # 🙋🏻‍♂ E4.0 man raising hand: light skin tone +1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ E4.0 man raising hand: medium-light skin tone +1F64B 1F3FC 200D 2642 ; minimally-qualified # 🙋🏼‍♂ E4.0 man raising hand: medium-light skin tone +1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ E4.0 man raising hand: medium skin tone +1F64B 1F3FD 200D 2642 ; minimally-qualified # 🙋🏽‍♂ E4.0 man raising hand: medium skin tone +1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ E4.0 man raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2642 ; minimally-qualified # 🙋🏾‍♂ E4.0 man raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ E4.0 man raising hand: dark skin tone +1F64B 1F3FF 200D 2642 ; minimally-qualified # 🙋🏿‍♂ E4.0 man raising hand: dark skin tone +1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ E4.0 woman raising hand +1F64B 200D 2640 ; minimally-qualified # 🙋‍♀ E4.0 woman raising hand +1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ E4.0 woman raising hand: light skin tone +1F64B 1F3FB 200D 2640 ; minimally-qualified # 🙋🏻‍♀ E4.0 woman raising hand: light skin tone +1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ E4.0 woman raising hand: medium-light skin tone +1F64B 1F3FC 200D 2640 ; minimally-qualified # 🙋🏼‍♀ E4.0 woman raising hand: medium-light skin tone +1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ E4.0 woman raising hand: medium skin tone +1F64B 1F3FD 200D 2640 ; minimally-qualified # 🙋🏽‍♀ E4.0 woman raising hand: medium skin tone +1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ E4.0 woman raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2640 ; minimally-qualified # 🙋🏾‍♀ E4.0 woman raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ E4.0 woman raising hand: dark skin tone +1F64B 1F3FF 200D 2640 ; minimally-qualified # 🙋🏿‍♀ E4.0 woman raising hand: dark skin tone +1F9CF ; fully-qualified # 🧏 E12.0 deaf person +1F9CF 1F3FB ; fully-qualified # 🧏🏻 E12.0 deaf person: light skin tone +1F9CF 1F3FC ; fully-qualified # 🧏🏼 E12.0 deaf person: medium-light skin tone +1F9CF 1F3FD ; fully-qualified # 🧏🏽 E12.0 deaf person: medium skin tone +1F9CF 1F3FE ; fully-qualified # 🧏🏾 E12.0 deaf person: medium-dark skin tone +1F9CF 1F3FF ; fully-qualified # 🧏🏿 E12.0 deaf person: dark skin tone +1F9CF 200D 2642 FE0F ; fully-qualified # 🧏‍♂️ E12.0 deaf man +1F9CF 200D 2642 ; minimally-qualified # 🧏‍♂ E12.0 deaf man +1F9CF 1F3FB 200D 2642 FE0F ; fully-qualified # 🧏🏻‍♂️ E12.0 deaf man: light skin tone +1F9CF 1F3FB 200D 2642 ; minimally-qualified # 🧏🏻‍♂ E12.0 deaf man: light skin tone +1F9CF 1F3FC 200D 2642 FE0F ; fully-qualified # 🧏🏼‍♂️ E12.0 deaf man: medium-light skin tone +1F9CF 1F3FC 200D 2642 ; minimally-qualified # 🧏🏼‍♂ E12.0 deaf man: medium-light skin tone +1F9CF 1F3FD 200D 2642 FE0F ; fully-qualified # 🧏🏽‍♂️ E12.0 deaf man: medium skin tone +1F9CF 1F3FD 200D 2642 ; minimally-qualified # 🧏🏽‍♂ E12.0 deaf man: medium skin tone +1F9CF 1F3FE 200D 2642 FE0F ; fully-qualified # 🧏🏾‍♂️ E12.0 deaf man: medium-dark skin tone +1F9CF 1F3FE 200D 2642 ; minimally-qualified # 🧏🏾‍♂ E12.0 deaf man: medium-dark skin tone +1F9CF 1F3FF 200D 2642 FE0F ; fully-qualified # 🧏🏿‍♂️ E12.0 deaf man: dark skin tone +1F9CF 1F3FF 200D 2642 ; minimally-qualified # 🧏🏿‍♂ E12.0 deaf man: dark skin tone +1F9CF 200D 2640 FE0F ; fully-qualified # 🧏‍♀️ E12.0 deaf woman +1F9CF 200D 2640 ; minimally-qualified # 🧏‍♀ E12.0 deaf woman +1F9CF 1F3FB 200D 2640 FE0F ; fully-qualified # 🧏🏻‍♀️ E12.0 deaf woman: light skin tone +1F9CF 1F3FB 200D 2640 ; minimally-qualified # 🧏🏻‍♀ E12.0 deaf woman: light skin tone +1F9CF 1F3FC 200D 2640 FE0F ; fully-qualified # 🧏🏼‍♀️ E12.0 deaf woman: medium-light skin tone +1F9CF 1F3FC 200D 2640 ; minimally-qualified # 🧏🏼‍♀ E12.0 deaf woman: medium-light skin tone +1F9CF 1F3FD 200D 2640 FE0F ; fully-qualified # 🧏🏽‍♀️ E12.0 deaf woman: medium skin tone +1F9CF 1F3FD 200D 2640 ; minimally-qualified # 🧏🏽‍♀ E12.0 deaf woman: medium skin tone +1F9CF 1F3FE 200D 2640 FE0F ; fully-qualified # 🧏🏾‍♀️ E12.0 deaf woman: medium-dark skin tone +1F9CF 1F3FE 200D 2640 ; minimally-qualified # 🧏🏾‍♀ E12.0 deaf woman: medium-dark skin tone +1F9CF 1F3FF 200D 2640 FE0F ; fully-qualified # 🧏🏿‍♀️ E12.0 deaf woman: dark skin tone +1F9CF 1F3FF 200D 2640 ; minimally-qualified # 🧏🏿‍♀ E12.0 deaf woman: dark skin tone +1F647 ; fully-qualified # 🙇 E0.6 person bowing +1F647 1F3FB ; fully-qualified # 🙇🏻 E1.0 person bowing: light skin tone +1F647 1F3FC ; fully-qualified # 🙇🏼 E1.0 person bowing: medium-light skin tone +1F647 1F3FD ; fully-qualified # 🙇🏽 E1.0 person bowing: medium skin tone +1F647 1F3FE ; fully-qualified # 🙇🏾 E1.0 person bowing: medium-dark skin tone +1F647 1F3FF ; fully-qualified # 🙇🏿 E1.0 person bowing: dark skin tone +1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ E4.0 man bowing +1F647 200D 2642 ; minimally-qualified # 🙇‍♂ E4.0 man bowing +1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ E4.0 man bowing: light skin tone +1F647 1F3FB 200D 2642 ; minimally-qualified # 🙇🏻‍♂ E4.0 man bowing: light skin tone +1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ E4.0 man bowing: medium-light skin tone +1F647 1F3FC 200D 2642 ; minimally-qualified # 🙇🏼‍♂ E4.0 man bowing: medium-light skin tone +1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ E4.0 man bowing: medium skin tone +1F647 1F3FD 200D 2642 ; minimally-qualified # 🙇🏽‍♂ E4.0 man bowing: medium skin tone +1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ E4.0 man bowing: medium-dark skin tone +1F647 1F3FE 200D 2642 ; minimally-qualified # 🙇🏾‍♂ E4.0 man bowing: medium-dark skin tone +1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ E4.0 man bowing: dark skin tone +1F647 1F3FF 200D 2642 ; minimally-qualified # 🙇🏿‍♂ E4.0 man bowing: dark skin tone +1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ E4.0 woman bowing +1F647 200D 2640 ; minimally-qualified # 🙇‍♀ E4.0 woman bowing +1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ E4.0 woman bowing: light skin tone +1F647 1F3FB 200D 2640 ; minimally-qualified # 🙇🏻‍♀ E4.0 woman bowing: light skin tone +1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ E4.0 woman bowing: medium-light skin tone +1F647 1F3FC 200D 2640 ; minimally-qualified # 🙇🏼‍♀ E4.0 woman bowing: medium-light skin tone +1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ E4.0 woman bowing: medium skin tone +1F647 1F3FD 200D 2640 ; minimally-qualified # 🙇🏽‍♀ E4.0 woman bowing: medium skin tone +1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ E4.0 woman bowing: medium-dark skin tone +1F647 1F3FE 200D 2640 ; minimally-qualified # 🙇🏾‍♀ E4.0 woman bowing: medium-dark skin tone +1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ E4.0 woman bowing: dark skin tone +1F647 1F3FF 200D 2640 ; minimally-qualified # 🙇🏿‍♀ E4.0 woman bowing: dark skin tone +1F926 ; fully-qualified # 🤦 E3.0 person facepalming +1F926 1F3FB ; fully-qualified # 🤦🏻 E3.0 person facepalming: light skin tone +1F926 1F3FC ; fully-qualified # 🤦🏼 E3.0 person facepalming: medium-light skin tone +1F926 1F3FD ; fully-qualified # 🤦🏽 E3.0 person facepalming: medium skin tone +1F926 1F3FE ; fully-qualified # 🤦🏾 E3.0 person facepalming: medium-dark skin tone +1F926 1F3FF ; fully-qualified # 🤦🏿 E3.0 person facepalming: dark skin tone +1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ E4.0 man facepalming +1F926 200D 2642 ; minimally-qualified # 🤦‍♂ E4.0 man facepalming +1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ E4.0 man facepalming: light skin tone +1F926 1F3FB 200D 2642 ; minimally-qualified # 🤦🏻‍♂ E4.0 man facepalming: light skin tone +1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ E4.0 man facepalming: medium-light skin tone +1F926 1F3FC 200D 2642 ; minimally-qualified # 🤦🏼‍♂ E4.0 man facepalming: medium-light skin tone +1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ E4.0 man facepalming: medium skin tone +1F926 1F3FD 200D 2642 ; minimally-qualified # 🤦🏽‍♂ E4.0 man facepalming: medium skin tone +1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ E4.0 man facepalming: medium-dark skin tone +1F926 1F3FE 200D 2642 ; minimally-qualified # 🤦🏾‍♂ E4.0 man facepalming: medium-dark skin tone +1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ E4.0 man facepalming: dark skin tone +1F926 1F3FF 200D 2642 ; minimally-qualified # 🤦🏿‍♂ E4.0 man facepalming: dark skin tone +1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ E4.0 woman facepalming +1F926 200D 2640 ; minimally-qualified # 🤦‍♀ E4.0 woman facepalming +1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ E4.0 woman facepalming: light skin tone +1F926 1F3FB 200D 2640 ; minimally-qualified # 🤦🏻‍♀ E4.0 woman facepalming: light skin tone +1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ E4.0 woman facepalming: medium-light skin tone +1F926 1F3FC 200D 2640 ; minimally-qualified # 🤦🏼‍♀ E4.0 woman facepalming: medium-light skin tone +1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ E4.0 woman facepalming: medium skin tone +1F926 1F3FD 200D 2640 ; minimally-qualified # 🤦🏽‍♀ E4.0 woman facepalming: medium skin tone +1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ E4.0 woman facepalming: medium-dark skin tone +1F926 1F3FE 200D 2640 ; minimally-qualified # 🤦🏾‍♀ E4.0 woman facepalming: medium-dark skin tone +1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ E4.0 woman facepalming: dark skin tone +1F926 1F3FF 200D 2640 ; minimally-qualified # 🤦🏿‍♀ E4.0 woman facepalming: dark skin tone +1F937 ; fully-qualified # 🤷 E3.0 person shrugging +1F937 1F3FB ; fully-qualified # 🤷🏻 E3.0 person shrugging: light skin tone +1F937 1F3FC ; fully-qualified # 🤷🏼 E3.0 person shrugging: medium-light skin tone +1F937 1F3FD ; fully-qualified # 🤷🏽 E3.0 person shrugging: medium skin tone +1F937 1F3FE ; fully-qualified # 🤷🏾 E3.0 person shrugging: medium-dark skin tone +1F937 1F3FF ; fully-qualified # 🤷🏿 E3.0 person shrugging: dark skin tone +1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ E4.0 man shrugging +1F937 200D 2642 ; minimally-qualified # 🤷‍♂ E4.0 man shrugging +1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ E4.0 man shrugging: light skin tone +1F937 1F3FB 200D 2642 ; minimally-qualified # 🤷🏻‍♂ E4.0 man shrugging: light skin tone +1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ E4.0 man shrugging: medium-light skin tone +1F937 1F3FC 200D 2642 ; minimally-qualified # 🤷🏼‍♂ E4.0 man shrugging: medium-light skin tone +1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ E4.0 man shrugging: medium skin tone +1F937 1F3FD 200D 2642 ; minimally-qualified # 🤷🏽‍♂ E4.0 man shrugging: medium skin tone +1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ E4.0 man shrugging: medium-dark skin tone +1F937 1F3FE 200D 2642 ; minimally-qualified # 🤷🏾‍♂ E4.0 man shrugging: medium-dark skin tone +1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ E4.0 man shrugging: dark skin tone +1F937 1F3FF 200D 2642 ; minimally-qualified # 🤷🏿‍♂ E4.0 man shrugging: dark skin tone +1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ E4.0 woman shrugging +1F937 200D 2640 ; minimally-qualified # 🤷‍♀ E4.0 woman shrugging +1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ E4.0 woman shrugging: light skin tone +1F937 1F3FB 200D 2640 ; minimally-qualified # 🤷🏻‍♀ E4.0 woman shrugging: light skin tone +1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ E4.0 woman shrugging: medium-light skin tone +1F937 1F3FC 200D 2640 ; minimally-qualified # 🤷🏼‍♀ E4.0 woman shrugging: medium-light skin tone +1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ E4.0 woman shrugging: medium skin tone +1F937 1F3FD 200D 2640 ; minimally-qualified # 🤷🏽‍♀ E4.0 woman shrugging: medium skin tone +1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ E4.0 woman shrugging: medium-dark skin tone +1F937 1F3FE 200D 2640 ; minimally-qualified # 🤷🏾‍♀ E4.0 woman shrugging: medium-dark skin tone +1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ E4.0 woman shrugging: dark skin tone +1F937 1F3FF 200D 2640 ; minimally-qualified # 🤷🏿‍♀ E4.0 woman shrugging: dark skin tone + +# subgroup: person-role +1F9D1 200D 2695 FE0F ; fully-qualified # 🧑‍⚕️ E12.1 health worker +1F9D1 200D 2695 ; minimally-qualified # 🧑‍⚕ E12.1 health worker +1F9D1 1F3FB 200D 2695 FE0F ; fully-qualified # 🧑🏻‍⚕️ E12.1 health worker: light skin tone +1F9D1 1F3FB 200D 2695 ; minimally-qualified # 🧑🏻‍⚕ E12.1 health worker: light skin tone +1F9D1 1F3FC 200D 2695 FE0F ; fully-qualified # 🧑🏼‍⚕️ E12.1 health worker: medium-light skin tone +1F9D1 1F3FC 200D 2695 ; minimally-qualified # 🧑🏼‍⚕ E12.1 health worker: medium-light skin tone +1F9D1 1F3FD 200D 2695 FE0F ; fully-qualified # 🧑🏽‍⚕️ E12.1 health worker: medium skin tone +1F9D1 1F3FD 200D 2695 ; minimally-qualified # 🧑🏽‍⚕ E12.1 health worker: medium skin tone +1F9D1 1F3FE 200D 2695 FE0F ; fully-qualified # 🧑🏾‍⚕️ E12.1 health worker: medium-dark skin tone +1F9D1 1F3FE 200D 2695 ; minimally-qualified # 🧑🏾‍⚕ E12.1 health worker: medium-dark skin tone +1F9D1 1F3FF 200D 2695 FE0F ; fully-qualified # 🧑🏿‍⚕️ E12.1 health worker: dark skin tone +1F9D1 1F3FF 200D 2695 ; minimally-qualified # 🧑🏿‍⚕ E12.1 health worker: dark skin tone +1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ E4.0 man health worker +1F468 200D 2695 ; minimally-qualified # 👨‍⚕ E4.0 man health worker +1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ E4.0 man health worker: light skin tone +1F468 1F3FB 200D 2695 ; minimally-qualified # 👨🏻‍⚕ E4.0 man health worker: light skin tone +1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ E4.0 man health worker: medium-light skin tone +1F468 1F3FC 200D 2695 ; minimally-qualified # 👨🏼‍⚕ E4.0 man health worker: medium-light skin tone +1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ E4.0 man health worker: medium skin tone +1F468 1F3FD 200D 2695 ; minimally-qualified # 👨🏽‍⚕ E4.0 man health worker: medium skin tone +1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ E4.0 man health worker: medium-dark skin tone +1F468 1F3FE 200D 2695 ; minimally-qualified # 👨🏾‍⚕ E4.0 man health worker: medium-dark skin tone +1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ E4.0 man health worker: dark skin tone +1F468 1F3FF 200D 2695 ; minimally-qualified # 👨🏿‍⚕ E4.0 man health worker: dark skin tone +1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ E4.0 woman health worker +1F469 200D 2695 ; minimally-qualified # 👩‍⚕ E4.0 woman health worker +1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ E4.0 woman health worker: light skin tone +1F469 1F3FB 200D 2695 ; minimally-qualified # 👩🏻‍⚕ E4.0 woman health worker: light skin tone +1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ E4.0 woman health worker: medium-light skin tone +1F469 1F3FC 200D 2695 ; minimally-qualified # 👩🏼‍⚕ E4.0 woman health worker: medium-light skin tone +1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ E4.0 woman health worker: medium skin tone +1F469 1F3FD 200D 2695 ; minimally-qualified # 👩🏽‍⚕ E4.0 woman health worker: medium skin tone +1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ E4.0 woman health worker: medium-dark skin tone +1F469 1F3FE 200D 2695 ; minimally-qualified # 👩🏾‍⚕ E4.0 woman health worker: medium-dark skin tone +1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ E4.0 woman health worker: dark skin tone +1F469 1F3FF 200D 2695 ; minimally-qualified # 👩🏿‍⚕ E4.0 woman health worker: dark skin tone +1F9D1 200D 1F393 ; fully-qualified # 🧑‍🎓 E12.1 student +1F9D1 1F3FB 200D 1F393 ; fully-qualified # 🧑🏻‍🎓 E12.1 student: light skin tone +1F9D1 1F3FC 200D 1F393 ; fully-qualified # 🧑🏼‍🎓 E12.1 student: medium-light skin tone +1F9D1 1F3FD 200D 1F393 ; fully-qualified # 🧑🏽‍🎓 E12.1 student: medium skin tone +1F9D1 1F3FE 200D 1F393 ; fully-qualified # 🧑🏾‍🎓 E12.1 student: medium-dark skin tone +1F9D1 1F3FF 200D 1F393 ; fully-qualified # 🧑🏿‍🎓 E12.1 student: dark skin tone +1F468 200D 1F393 ; fully-qualified # 👨‍🎓 E4.0 man student +1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 E4.0 man student: light skin tone +1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 E4.0 man student: medium-light skin tone +1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 E4.0 man student: medium skin tone +1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 E4.0 man student: medium-dark skin tone +1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 E4.0 man student: dark skin tone +1F469 200D 1F393 ; fully-qualified # 👩‍🎓 E4.0 woman student +1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 E4.0 woman student: light skin tone +1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 E4.0 woman student: medium-light skin tone +1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 E4.0 woman student: medium skin tone +1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 E4.0 woman student: medium-dark skin tone +1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 E4.0 woman student: dark skin tone +1F9D1 200D 1F3EB ; fully-qualified # 🧑‍🏫 E12.1 teacher +1F9D1 1F3FB 200D 1F3EB ; fully-qualified # 🧑🏻‍🏫 E12.1 teacher: light skin tone +1F9D1 1F3FC 200D 1F3EB ; fully-qualified # 🧑🏼‍🏫 E12.1 teacher: medium-light skin tone +1F9D1 1F3FD 200D 1F3EB ; fully-qualified # 🧑🏽‍🏫 E12.1 teacher: medium skin tone +1F9D1 1F3FE 200D 1F3EB ; fully-qualified # 🧑🏾‍🏫 E12.1 teacher: medium-dark skin tone +1F9D1 1F3FF 200D 1F3EB ; fully-qualified # 🧑🏿‍🏫 E12.1 teacher: dark skin tone +1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 E4.0 man teacher +1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 E4.0 man teacher: light skin tone +1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 E4.0 man teacher: medium-light skin tone +1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 E4.0 man teacher: medium skin tone +1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 E4.0 man teacher: medium-dark skin tone +1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 E4.0 man teacher: dark skin tone +1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 E4.0 woman teacher +1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 E4.0 woman teacher: light skin tone +1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 E4.0 woman teacher: medium-light skin tone +1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 E4.0 woman teacher: medium skin tone +1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 E4.0 woman teacher: medium-dark skin tone +1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 E4.0 woman teacher: dark skin tone +1F9D1 200D 2696 FE0F ; fully-qualified # 🧑‍⚖️ E12.1 judge +1F9D1 200D 2696 ; minimally-qualified # 🧑‍⚖ E12.1 judge +1F9D1 1F3FB 200D 2696 FE0F ; fully-qualified # 🧑🏻‍⚖️ E12.1 judge: light skin tone +1F9D1 1F3FB 200D 2696 ; minimally-qualified # 🧑🏻‍⚖ E12.1 judge: light skin tone +1F9D1 1F3FC 200D 2696 FE0F ; fully-qualified # 🧑🏼‍⚖️ E12.1 judge: medium-light skin tone +1F9D1 1F3FC 200D 2696 ; minimally-qualified # 🧑🏼‍⚖ E12.1 judge: medium-light skin tone +1F9D1 1F3FD 200D 2696 FE0F ; fully-qualified # 🧑🏽‍⚖️ E12.1 judge: medium skin tone +1F9D1 1F3FD 200D 2696 ; minimally-qualified # 🧑🏽‍⚖ E12.1 judge: medium skin tone +1F9D1 1F3FE 200D 2696 FE0F ; fully-qualified # 🧑🏾‍⚖️ E12.1 judge: medium-dark skin tone +1F9D1 1F3FE 200D 2696 ; minimally-qualified # 🧑🏾‍⚖ E12.1 judge: medium-dark skin tone +1F9D1 1F3FF 200D 2696 FE0F ; fully-qualified # 🧑🏿‍⚖️ E12.1 judge: dark skin tone +1F9D1 1F3FF 200D 2696 ; minimally-qualified # 🧑🏿‍⚖ E12.1 judge: dark skin tone +1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ E4.0 man judge +1F468 200D 2696 ; minimally-qualified # 👨‍⚖ E4.0 man judge +1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ E4.0 man judge: light skin tone +1F468 1F3FB 200D 2696 ; minimally-qualified # 👨🏻‍⚖ E4.0 man judge: light skin tone +1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ E4.0 man judge: medium-light skin tone +1F468 1F3FC 200D 2696 ; minimally-qualified # 👨🏼‍⚖ E4.0 man judge: medium-light skin tone +1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ E4.0 man judge: medium skin tone +1F468 1F3FD 200D 2696 ; minimally-qualified # 👨🏽‍⚖ E4.0 man judge: medium skin tone +1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ E4.0 man judge: medium-dark skin tone +1F468 1F3FE 200D 2696 ; minimally-qualified # 👨🏾‍⚖ E4.0 man judge: medium-dark skin tone +1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ E4.0 man judge: dark skin tone +1F468 1F3FF 200D 2696 ; minimally-qualified # 👨🏿‍⚖ E4.0 man judge: dark skin tone +1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ E4.0 woman judge +1F469 200D 2696 ; minimally-qualified # 👩‍⚖ E4.0 woman judge +1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ E4.0 woman judge: light skin tone +1F469 1F3FB 200D 2696 ; minimally-qualified # 👩🏻‍⚖ E4.0 woman judge: light skin tone +1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ E4.0 woman judge: medium-light skin tone +1F469 1F3FC 200D 2696 ; minimally-qualified # 👩🏼‍⚖ E4.0 woman judge: medium-light skin tone +1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ E4.0 woman judge: medium skin tone +1F469 1F3FD 200D 2696 ; minimally-qualified # 👩🏽‍⚖ E4.0 woman judge: medium skin tone +1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ E4.0 woman judge: medium-dark skin tone +1F469 1F3FE 200D 2696 ; minimally-qualified # 👩🏾‍⚖ E4.0 woman judge: medium-dark skin tone +1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ E4.0 woman judge: dark skin tone +1F469 1F3FF 200D 2696 ; minimally-qualified # 👩🏿‍⚖ E4.0 woman judge: dark skin tone +1F9D1 200D 1F33E ; fully-qualified # 🧑‍🌾 E12.1 farmer +1F9D1 1F3FB 200D 1F33E ; fully-qualified # 🧑🏻‍🌾 E12.1 farmer: light skin tone +1F9D1 1F3FC 200D 1F33E ; fully-qualified # 🧑🏼‍🌾 E12.1 farmer: medium-light skin tone +1F9D1 1F3FD 200D 1F33E ; fully-qualified # 🧑🏽‍🌾 E12.1 farmer: medium skin tone +1F9D1 1F3FE 200D 1F33E ; fully-qualified # 🧑🏾‍🌾 E12.1 farmer: medium-dark skin tone +1F9D1 1F3FF 200D 1F33E ; fully-qualified # 🧑🏿‍🌾 E12.1 farmer: dark skin tone +1F468 200D 1F33E ; fully-qualified # 👨‍🌾 E4.0 man farmer +1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 E4.0 man farmer: light skin tone +1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 E4.0 man farmer: medium-light skin tone +1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 E4.0 man farmer: medium skin tone +1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 E4.0 man farmer: medium-dark skin tone +1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 E4.0 man farmer: dark skin tone +1F469 200D 1F33E ; fully-qualified # 👩‍🌾 E4.0 woman farmer +1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 E4.0 woman farmer: light skin tone +1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 E4.0 woman farmer: medium-light skin tone +1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 E4.0 woman farmer: medium skin tone +1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 E4.0 woman farmer: medium-dark skin tone +1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 E4.0 woman farmer: dark skin tone +1F9D1 200D 1F373 ; fully-qualified # 🧑‍🍳 E12.1 cook +1F9D1 1F3FB 200D 1F373 ; fully-qualified # 🧑🏻‍🍳 E12.1 cook: light skin tone +1F9D1 1F3FC 200D 1F373 ; fully-qualified # 🧑🏼‍🍳 E12.1 cook: medium-light skin tone +1F9D1 1F3FD 200D 1F373 ; fully-qualified # 🧑🏽‍🍳 E12.1 cook: medium skin tone +1F9D1 1F3FE 200D 1F373 ; fully-qualified # 🧑🏾‍🍳 E12.1 cook: medium-dark skin tone +1F9D1 1F3FF 200D 1F373 ; fully-qualified # 🧑🏿‍🍳 E12.1 cook: dark skin tone +1F468 200D 1F373 ; fully-qualified # 👨‍🍳 E4.0 man cook +1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 E4.0 man cook: light skin tone +1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 E4.0 man cook: medium-light skin tone +1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 E4.0 man cook: medium skin tone +1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 E4.0 man cook: medium-dark skin tone +1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 E4.0 man cook: dark skin tone +1F469 200D 1F373 ; fully-qualified # 👩‍🍳 E4.0 woman cook +1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 E4.0 woman cook: light skin tone +1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 E4.0 woman cook: medium-light skin tone +1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 E4.0 woman cook: medium skin tone +1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 E4.0 woman cook: medium-dark skin tone +1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 E4.0 woman cook: dark skin tone +1F9D1 200D 1F527 ; fully-qualified # 🧑‍🔧 E12.1 mechanic +1F9D1 1F3FB 200D 1F527 ; fully-qualified # 🧑🏻‍🔧 E12.1 mechanic: light skin tone +1F9D1 1F3FC 200D 1F527 ; fully-qualified # 🧑🏼‍🔧 E12.1 mechanic: medium-light skin tone +1F9D1 1F3FD 200D 1F527 ; fully-qualified # 🧑🏽‍🔧 E12.1 mechanic: medium skin tone +1F9D1 1F3FE 200D 1F527 ; fully-qualified # 🧑🏾‍🔧 E12.1 mechanic: medium-dark skin tone +1F9D1 1F3FF 200D 1F527 ; fully-qualified # 🧑🏿‍🔧 E12.1 mechanic: dark skin tone +1F468 200D 1F527 ; fully-qualified # 👨‍🔧 E4.0 man mechanic +1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 E4.0 man mechanic: light skin tone +1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 E4.0 man mechanic: medium-light skin tone +1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 E4.0 man mechanic: medium skin tone +1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 E4.0 man mechanic: medium-dark skin tone +1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 E4.0 man mechanic: dark skin tone +1F469 200D 1F527 ; fully-qualified # 👩‍🔧 E4.0 woman mechanic +1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 E4.0 woman mechanic: light skin tone +1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 E4.0 woman mechanic: medium-light skin tone +1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 E4.0 woman mechanic: medium skin tone +1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 E4.0 woman mechanic: medium-dark skin tone +1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 E4.0 woman mechanic: dark skin tone +1F9D1 200D 1F3ED ; fully-qualified # 🧑‍🏭 E12.1 factory worker +1F9D1 1F3FB 200D 1F3ED ; fully-qualified # 🧑🏻‍🏭 E12.1 factory worker: light skin tone +1F9D1 1F3FC 200D 1F3ED ; fully-qualified # 🧑🏼‍🏭 E12.1 factory worker: medium-light skin tone +1F9D1 1F3FD 200D 1F3ED ; fully-qualified # 🧑🏽‍🏭 E12.1 factory worker: medium skin tone +1F9D1 1F3FE 200D 1F3ED ; fully-qualified # 🧑🏾‍🏭 E12.1 factory worker: medium-dark skin tone +1F9D1 1F3FF 200D 1F3ED ; fully-qualified # 🧑🏿‍🏭 E12.1 factory worker: dark skin tone +1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 E4.0 man factory worker +1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 E4.0 man factory worker: light skin tone +1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 E4.0 man factory worker: medium-light skin tone +1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 E4.0 man factory worker: medium skin tone +1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 E4.0 man factory worker: medium-dark skin tone +1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 E4.0 man factory worker: dark skin tone +1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 E4.0 woman factory worker +1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 E4.0 woman factory worker: light skin tone +1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 E4.0 woman factory worker: medium-light skin tone +1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 E4.0 woman factory worker: medium skin tone +1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 E4.0 woman factory worker: medium-dark skin tone +1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 E4.0 woman factory worker: dark skin tone +1F9D1 200D 1F4BC ; fully-qualified # 🧑‍💼 E12.1 office worker +1F9D1 1F3FB 200D 1F4BC ; fully-qualified # 🧑🏻‍💼 E12.1 office worker: light skin tone +1F9D1 1F3FC 200D 1F4BC ; fully-qualified # 🧑🏼‍💼 E12.1 office worker: medium-light skin tone +1F9D1 1F3FD 200D 1F4BC ; fully-qualified # 🧑🏽‍💼 E12.1 office worker: medium skin tone +1F9D1 1F3FE 200D 1F4BC ; fully-qualified # 🧑🏾‍💼 E12.1 office worker: medium-dark skin tone +1F9D1 1F3FF 200D 1F4BC ; fully-qualified # 🧑🏿‍💼 E12.1 office worker: dark skin tone +1F468 200D 1F4BC ; fully-qualified # 👨‍💼 E4.0 man office worker +1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 E4.0 man office worker: light skin tone +1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 E4.0 man office worker: medium-light skin tone +1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 E4.0 man office worker: medium skin tone +1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 E4.0 man office worker: medium-dark skin tone +1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 E4.0 man office worker: dark skin tone +1F469 200D 1F4BC ; fully-qualified # 👩‍💼 E4.0 woman office worker +1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 E4.0 woman office worker: light skin tone +1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 E4.0 woman office worker: medium-light skin tone +1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 E4.0 woman office worker: medium skin tone +1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 E4.0 woman office worker: medium-dark skin tone +1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 E4.0 woman office worker: dark skin tone +1F9D1 200D 1F52C ; fully-qualified # 🧑‍🔬 E12.1 scientist +1F9D1 1F3FB 200D 1F52C ; fully-qualified # 🧑🏻‍🔬 E12.1 scientist: light skin tone +1F9D1 1F3FC 200D 1F52C ; fully-qualified # 🧑🏼‍🔬 E12.1 scientist: medium-light skin tone +1F9D1 1F3FD 200D 1F52C ; fully-qualified # 🧑🏽‍🔬 E12.1 scientist: medium skin tone +1F9D1 1F3FE 200D 1F52C ; fully-qualified # 🧑🏾‍🔬 E12.1 scientist: medium-dark skin tone +1F9D1 1F3FF 200D 1F52C ; fully-qualified # 🧑🏿‍🔬 E12.1 scientist: dark skin tone +1F468 200D 1F52C ; fully-qualified # 👨‍🔬 E4.0 man scientist +1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 E4.0 man scientist: light skin tone +1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 E4.0 man scientist: medium-light skin tone +1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 E4.0 man scientist: medium skin tone +1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 E4.0 man scientist: medium-dark skin tone +1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 E4.0 man scientist: dark skin tone +1F469 200D 1F52C ; fully-qualified # 👩‍🔬 E4.0 woman scientist +1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 E4.0 woman scientist: light skin tone +1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 E4.0 woman scientist: medium-light skin tone +1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 E4.0 woman scientist: medium skin tone +1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 E4.0 woman scientist: medium-dark skin tone +1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 E4.0 woman scientist: dark skin tone +1F9D1 200D 1F4BB ; fully-qualified # 🧑‍💻 E12.1 technologist +1F9D1 1F3FB 200D 1F4BB ; fully-qualified # 🧑🏻‍💻 E12.1 technologist: light skin tone +1F9D1 1F3FC 200D 1F4BB ; fully-qualified # 🧑🏼‍💻 E12.1 technologist: medium-light skin tone +1F9D1 1F3FD 200D 1F4BB ; fully-qualified # 🧑🏽‍💻 E12.1 technologist: medium skin tone +1F9D1 1F3FE 200D 1F4BB ; fully-qualified # 🧑🏾‍💻 E12.1 technologist: medium-dark skin tone +1F9D1 1F3FF 200D 1F4BB ; fully-qualified # 🧑🏿‍💻 E12.1 technologist: dark skin tone +1F468 200D 1F4BB ; fully-qualified # 👨‍💻 E4.0 man technologist +1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 E4.0 man technologist: light skin tone +1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 E4.0 man technologist: medium-light skin tone +1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 E4.0 man technologist: medium skin tone +1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 E4.0 man technologist: medium-dark skin tone +1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 E4.0 man technologist: dark skin tone +1F469 200D 1F4BB ; fully-qualified # 👩‍💻 E4.0 woman technologist +1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 E4.0 woman technologist: light skin tone +1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 E4.0 woman technologist: medium-light skin tone +1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 E4.0 woman technologist: medium skin tone +1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 E4.0 woman technologist: medium-dark skin tone +1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 E4.0 woman technologist: dark skin tone +1F9D1 200D 1F3A4 ; fully-qualified # 🧑‍🎤 E12.1 singer +1F9D1 1F3FB 200D 1F3A4 ; fully-qualified # 🧑🏻‍🎤 E12.1 singer: light skin tone +1F9D1 1F3FC 200D 1F3A4 ; fully-qualified # 🧑🏼‍🎤 E12.1 singer: medium-light skin tone +1F9D1 1F3FD 200D 1F3A4 ; fully-qualified # 🧑🏽‍🎤 E12.1 singer: medium skin tone +1F9D1 1F3FE 200D 1F3A4 ; fully-qualified # 🧑🏾‍🎤 E12.1 singer: medium-dark skin tone +1F9D1 1F3FF 200D 1F3A4 ; fully-qualified # 🧑🏿‍🎤 E12.1 singer: dark skin tone +1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 E4.0 man singer +1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 E4.0 man singer: light skin tone +1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 E4.0 man singer: medium-light skin tone +1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 E4.0 man singer: medium skin tone +1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 E4.0 man singer: medium-dark skin tone +1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 E4.0 man singer: dark skin tone +1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 E4.0 woman singer +1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 E4.0 woman singer: light skin tone +1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 E4.0 woman singer: medium-light skin tone +1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 E4.0 woman singer: medium skin tone +1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 E4.0 woman singer: medium-dark skin tone +1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 E4.0 woman singer: dark skin tone +1F9D1 200D 1F3A8 ; fully-qualified # 🧑‍🎨 E12.1 artist +1F9D1 1F3FB 200D 1F3A8 ; fully-qualified # 🧑🏻‍🎨 E12.1 artist: light skin tone +1F9D1 1F3FC 200D 1F3A8 ; fully-qualified # 🧑🏼‍🎨 E12.1 artist: medium-light skin tone +1F9D1 1F3FD 200D 1F3A8 ; fully-qualified # 🧑🏽‍🎨 E12.1 artist: medium skin tone +1F9D1 1F3FE 200D 1F3A8 ; fully-qualified # 🧑🏾‍🎨 E12.1 artist: medium-dark skin tone +1F9D1 1F3FF 200D 1F3A8 ; fully-qualified # 🧑🏿‍🎨 E12.1 artist: dark skin tone +1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 E4.0 man artist +1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 E4.0 man artist: light skin tone +1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 E4.0 man artist: medium-light skin tone +1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 E4.0 man artist: medium skin tone +1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 E4.0 man artist: medium-dark skin tone +1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 E4.0 man artist: dark skin tone +1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 E4.0 woman artist +1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 E4.0 woman artist: light skin tone +1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 E4.0 woman artist: medium-light skin tone +1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 E4.0 woman artist: medium skin tone +1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 E4.0 woman artist: medium-dark skin tone +1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 E4.0 woman artist: dark skin tone +1F9D1 200D 2708 FE0F ; fully-qualified # 🧑‍✈️ E12.1 pilot +1F9D1 200D 2708 ; minimally-qualified # 🧑‍✈ E12.1 pilot +1F9D1 1F3FB 200D 2708 FE0F ; fully-qualified # 🧑🏻‍✈️ E12.1 pilot: light skin tone +1F9D1 1F3FB 200D 2708 ; minimally-qualified # 🧑🏻‍✈ E12.1 pilot: light skin tone +1F9D1 1F3FC 200D 2708 FE0F ; fully-qualified # 🧑🏼‍✈️ E12.1 pilot: medium-light skin tone +1F9D1 1F3FC 200D 2708 ; minimally-qualified # 🧑🏼‍✈ E12.1 pilot: medium-light skin tone +1F9D1 1F3FD 200D 2708 FE0F ; fully-qualified # 🧑🏽‍✈️ E12.1 pilot: medium skin tone +1F9D1 1F3FD 200D 2708 ; minimally-qualified # 🧑🏽‍✈ E12.1 pilot: medium skin tone +1F9D1 1F3FE 200D 2708 FE0F ; fully-qualified # 🧑🏾‍✈️ E12.1 pilot: medium-dark skin tone +1F9D1 1F3FE 200D 2708 ; minimally-qualified # 🧑🏾‍✈ E12.1 pilot: medium-dark skin tone +1F9D1 1F3FF 200D 2708 FE0F ; fully-qualified # 🧑🏿‍✈️ E12.1 pilot: dark skin tone +1F9D1 1F3FF 200D 2708 ; minimally-qualified # 🧑🏿‍✈ E12.1 pilot: dark skin tone +1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ E4.0 man pilot +1F468 200D 2708 ; minimally-qualified # 👨‍✈ E4.0 man pilot +1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ E4.0 man pilot: light skin tone +1F468 1F3FB 200D 2708 ; minimally-qualified # 👨🏻‍✈ E4.0 man pilot: light skin tone +1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ E4.0 man pilot: medium-light skin tone +1F468 1F3FC 200D 2708 ; minimally-qualified # 👨🏼‍✈ E4.0 man pilot: medium-light skin tone +1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ E4.0 man pilot: medium skin tone +1F468 1F3FD 200D 2708 ; minimally-qualified # 👨🏽‍✈ E4.0 man pilot: medium skin tone +1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ E4.0 man pilot: medium-dark skin tone +1F468 1F3FE 200D 2708 ; minimally-qualified # 👨🏾‍✈ E4.0 man pilot: medium-dark skin tone +1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ E4.0 man pilot: dark skin tone +1F468 1F3FF 200D 2708 ; minimally-qualified # 👨🏿‍✈ E4.0 man pilot: dark skin tone +1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ E4.0 woman pilot +1F469 200D 2708 ; minimally-qualified # 👩‍✈ E4.0 woman pilot +1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ E4.0 woman pilot: light skin tone +1F469 1F3FB 200D 2708 ; minimally-qualified # 👩🏻‍✈ E4.0 woman pilot: light skin tone +1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ E4.0 woman pilot: medium-light skin tone +1F469 1F3FC 200D 2708 ; minimally-qualified # 👩🏼‍✈ E4.0 woman pilot: medium-light skin tone +1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ E4.0 woman pilot: medium skin tone +1F469 1F3FD 200D 2708 ; minimally-qualified # 👩🏽‍✈ E4.0 woman pilot: medium skin tone +1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ E4.0 woman pilot: medium-dark skin tone +1F469 1F3FE 200D 2708 ; minimally-qualified # 👩🏾‍✈ E4.0 woman pilot: medium-dark skin tone +1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ E4.0 woman pilot: dark skin tone +1F469 1F3FF 200D 2708 ; minimally-qualified # 👩🏿‍✈ E4.0 woman pilot: dark skin tone +1F9D1 200D 1F680 ; fully-qualified # 🧑‍🚀 E12.1 astronaut +1F9D1 1F3FB 200D 1F680 ; fully-qualified # 🧑🏻‍🚀 E12.1 astronaut: light skin tone +1F9D1 1F3FC 200D 1F680 ; fully-qualified # 🧑🏼‍🚀 E12.1 astronaut: medium-light skin tone +1F9D1 1F3FD 200D 1F680 ; fully-qualified # 🧑🏽‍🚀 E12.1 astronaut: medium skin tone +1F9D1 1F3FE 200D 1F680 ; fully-qualified # 🧑🏾‍🚀 E12.1 astronaut: medium-dark skin tone +1F9D1 1F3FF 200D 1F680 ; fully-qualified # 🧑🏿‍🚀 E12.1 astronaut: dark skin tone +1F468 200D 1F680 ; fully-qualified # 👨‍🚀 E4.0 man astronaut +1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 E4.0 man astronaut: light skin tone +1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 E4.0 man astronaut: medium-light skin tone +1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 E4.0 man astronaut: medium skin tone +1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 E4.0 man astronaut: medium-dark skin tone +1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 E4.0 man astronaut: dark skin tone +1F469 200D 1F680 ; fully-qualified # 👩‍🚀 E4.0 woman astronaut +1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 E4.0 woman astronaut: light skin tone +1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 E4.0 woman astronaut: medium-light skin tone +1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 E4.0 woman astronaut: medium skin tone +1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 E4.0 woman astronaut: medium-dark skin tone +1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 E4.0 woman astronaut: dark skin tone +1F9D1 200D 1F692 ; fully-qualified # 🧑‍🚒 E12.1 firefighter +1F9D1 1F3FB 200D 1F692 ; fully-qualified # 🧑🏻‍🚒 E12.1 firefighter: light skin tone +1F9D1 1F3FC 200D 1F692 ; fully-qualified # 🧑🏼‍🚒 E12.1 firefighter: medium-light skin tone +1F9D1 1F3FD 200D 1F692 ; fully-qualified # 🧑🏽‍🚒 E12.1 firefighter: medium skin tone +1F9D1 1F3FE 200D 1F692 ; fully-qualified # 🧑🏾‍🚒 E12.1 firefighter: medium-dark skin tone +1F9D1 1F3FF 200D 1F692 ; fully-qualified # 🧑🏿‍🚒 E12.1 firefighter: dark skin tone +1F468 200D 1F692 ; fully-qualified # 👨‍🚒 E4.0 man firefighter +1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 E4.0 man firefighter: light skin tone +1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 E4.0 man firefighter: medium-light skin tone +1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 E4.0 man firefighter: medium skin tone +1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 E4.0 man firefighter: medium-dark skin tone +1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 E4.0 man firefighter: dark skin tone +1F469 200D 1F692 ; fully-qualified # 👩‍🚒 E4.0 woman firefighter +1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 E4.0 woman firefighter: light skin tone +1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 E4.0 woman firefighter: medium-light skin tone +1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 E4.0 woman firefighter: medium skin tone +1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 E4.0 woman firefighter: medium-dark skin tone +1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 E4.0 woman firefighter: dark skin tone +1F46E ; fully-qualified # 👮 E0.6 police officer +1F46E 1F3FB ; fully-qualified # 👮🏻 E1.0 police officer: light skin tone +1F46E 1F3FC ; fully-qualified # 👮🏼 E1.0 police officer: medium-light skin tone +1F46E 1F3FD ; fully-qualified # 👮🏽 E1.0 police officer: medium skin tone +1F46E 1F3FE ; fully-qualified # 👮🏾 E1.0 police officer: medium-dark skin tone +1F46E 1F3FF ; fully-qualified # 👮🏿 E1.0 police officer: dark skin tone +1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ E4.0 man police officer +1F46E 200D 2642 ; minimally-qualified # 👮‍♂ E4.0 man police officer +1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ E4.0 man police officer: light skin tone +1F46E 1F3FB 200D 2642 ; minimally-qualified # 👮🏻‍♂ E4.0 man police officer: light skin tone +1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ E4.0 man police officer: medium-light skin tone +1F46E 1F3FC 200D 2642 ; minimally-qualified # 👮🏼‍♂ E4.0 man police officer: medium-light skin tone +1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ E4.0 man police officer: medium skin tone +1F46E 1F3FD 200D 2642 ; minimally-qualified # 👮🏽‍♂ E4.0 man police officer: medium skin tone +1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ E4.0 man police officer: medium-dark skin tone +1F46E 1F3FE 200D 2642 ; minimally-qualified # 👮🏾‍♂ E4.0 man police officer: medium-dark skin tone +1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ E4.0 man police officer: dark skin tone +1F46E 1F3FF 200D 2642 ; minimally-qualified # 👮🏿‍♂ E4.0 man police officer: dark skin tone +1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ E4.0 woman police officer +1F46E 200D 2640 ; minimally-qualified # 👮‍♀ E4.0 woman police officer +1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ E4.0 woman police officer: light skin tone +1F46E 1F3FB 200D 2640 ; minimally-qualified # 👮🏻‍♀ E4.0 woman police officer: light skin tone +1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ E4.0 woman police officer: medium-light skin tone +1F46E 1F3FC 200D 2640 ; minimally-qualified # 👮🏼‍♀ E4.0 woman police officer: medium-light skin tone +1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ E4.0 woman police officer: medium skin tone +1F46E 1F3FD 200D 2640 ; minimally-qualified # 👮🏽‍♀ E4.0 woman police officer: medium skin tone +1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ E4.0 woman police officer: medium-dark skin tone +1F46E 1F3FE 200D 2640 ; minimally-qualified # 👮🏾‍♀ E4.0 woman police officer: medium-dark skin tone +1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ E4.0 woman police officer: dark skin tone +1F46E 1F3FF 200D 2640 ; minimally-qualified # 👮🏿‍♀ E4.0 woman police officer: dark skin tone +1F575 FE0F ; fully-qualified # 🕵️ E0.7 detective +1F575 ; unqualified # 🕵 E0.7 detective +1F575 1F3FB ; fully-qualified # 🕵🏻 E2.0 detective: light skin tone +1F575 1F3FC ; fully-qualified # 🕵🏼 E2.0 detective: medium-light skin tone +1F575 1F3FD ; fully-qualified # 🕵🏽 E2.0 detective: medium skin tone +1F575 1F3FE ; fully-qualified # 🕵🏾 E2.0 detective: medium-dark skin tone +1F575 1F3FF ; fully-qualified # 🕵🏿 E2.0 detective: dark skin tone +1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ E4.0 man detective +1F575 200D 2642 FE0F ; unqualified # 🕵‍♂️ E4.0 man detective +1F575 FE0F 200D 2642 ; unqualified # 🕵️‍♂ E4.0 man detective +1F575 200D 2642 ; unqualified # 🕵‍♂ E4.0 man detective +1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ E4.0 man detective: light skin tone +1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻‍♂ E4.0 man detective: light skin tone +1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ E4.0 man detective: medium-light skin tone +1F575 1F3FC 200D 2642 ; minimally-qualified # 🕵🏼‍♂ E4.0 man detective: medium-light skin tone +1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ E4.0 man detective: medium skin tone +1F575 1F3FD 200D 2642 ; minimally-qualified # 🕵🏽‍♂ E4.0 man detective: medium skin tone +1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ E4.0 man detective: medium-dark skin tone +1F575 1F3FE 200D 2642 ; minimally-qualified # 🕵🏾‍♂ E4.0 man detective: medium-dark skin tone +1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ E4.0 man detective: dark skin tone +1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿‍♂ E4.0 man detective: dark skin tone +1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ E4.0 woman detective +1F575 200D 2640 FE0F ; unqualified # 🕵‍♀️ E4.0 woman detective +1F575 FE0F 200D 2640 ; unqualified # 🕵️‍♀ E4.0 woman detective +1F575 200D 2640 ; unqualified # 🕵‍♀ E4.0 woman detective +1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ E4.0 woman detective: light skin tone +1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻‍♀ E4.0 woman detective: light skin tone +1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ E4.0 woman detective: medium-light skin tone +1F575 1F3FC 200D 2640 ; minimally-qualified # 🕵🏼‍♀ E4.0 woman detective: medium-light skin tone +1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ E4.0 woman detective: medium skin tone +1F575 1F3FD 200D 2640 ; minimally-qualified # 🕵🏽‍♀ E4.0 woman detective: medium skin tone +1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ E4.0 woman detective: medium-dark skin tone +1F575 1F3FE 200D 2640 ; minimally-qualified # 🕵🏾‍♀ E4.0 woman detective: medium-dark skin tone +1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ E4.0 woman detective: dark skin tone +1F575 1F3FF 200D 2640 ; minimally-qualified # 🕵🏿‍♀ E4.0 woman detective: dark skin tone +1F482 ; fully-qualified # 💂 E0.6 guard +1F482 1F3FB ; fully-qualified # 💂🏻 E1.0 guard: light skin tone +1F482 1F3FC ; fully-qualified # 💂🏼 E1.0 guard: medium-light skin tone +1F482 1F3FD ; fully-qualified # 💂🏽 E1.0 guard: medium skin tone +1F482 1F3FE ; fully-qualified # 💂🏾 E1.0 guard: medium-dark skin tone +1F482 1F3FF ; fully-qualified # 💂🏿 E1.0 guard: dark skin tone +1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ E4.0 man guard +1F482 200D 2642 ; minimally-qualified # 💂‍♂ E4.0 man guard +1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ E4.0 man guard: light skin tone +1F482 1F3FB 200D 2642 ; minimally-qualified # 💂🏻‍♂ E4.0 man guard: light skin tone +1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ E4.0 man guard: medium-light skin tone +1F482 1F3FC 200D 2642 ; minimally-qualified # 💂🏼‍♂ E4.0 man guard: medium-light skin tone +1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ E4.0 man guard: medium skin tone +1F482 1F3FD 200D 2642 ; minimally-qualified # 💂🏽‍♂ E4.0 man guard: medium skin tone +1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ E4.0 man guard: medium-dark skin tone +1F482 1F3FE 200D 2642 ; minimally-qualified # 💂🏾‍♂ E4.0 man guard: medium-dark skin tone +1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ E4.0 man guard: dark skin tone +1F482 1F3FF 200D 2642 ; minimally-qualified # 💂🏿‍♂ E4.0 man guard: dark skin tone +1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ E4.0 woman guard +1F482 200D 2640 ; minimally-qualified # 💂‍♀ E4.0 woman guard +1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ E4.0 woman guard: light skin tone +1F482 1F3FB 200D 2640 ; minimally-qualified # 💂🏻‍♀ E4.0 woman guard: light skin tone +1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ E4.0 woman guard: medium-light skin tone +1F482 1F3FC 200D 2640 ; minimally-qualified # 💂🏼‍♀ E4.0 woman guard: medium-light skin tone +1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ E4.0 woman guard: medium skin tone +1F482 1F3FD 200D 2640 ; minimally-qualified # 💂🏽‍♀ E4.0 woman guard: medium skin tone +1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ E4.0 woman guard: medium-dark skin tone +1F482 1F3FE 200D 2640 ; minimally-qualified # 💂🏾‍♀ E4.0 woman guard: medium-dark skin tone +1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ E4.0 woman guard: dark skin tone +1F482 1F3FF 200D 2640 ; minimally-qualified # 💂🏿‍♀ E4.0 woman guard: dark skin tone +1F477 ; fully-qualified # 👷 E0.6 construction worker +1F477 1F3FB ; fully-qualified # 👷🏻 E1.0 construction worker: light skin tone +1F477 1F3FC ; fully-qualified # 👷🏼 E1.0 construction worker: medium-light skin tone +1F477 1F3FD ; fully-qualified # 👷🏽 E1.0 construction worker: medium skin tone +1F477 1F3FE ; fully-qualified # 👷🏾 E1.0 construction worker: medium-dark skin tone +1F477 1F3FF ; fully-qualified # 👷🏿 E1.0 construction worker: dark skin tone +1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ E4.0 man construction worker +1F477 200D 2642 ; minimally-qualified # 👷‍♂ E4.0 man construction worker +1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ E4.0 man construction worker: light skin tone +1F477 1F3FB 200D 2642 ; minimally-qualified # 👷🏻‍♂ E4.0 man construction worker: light skin tone +1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ E4.0 man construction worker: medium-light skin tone +1F477 1F3FC 200D 2642 ; minimally-qualified # 👷🏼‍♂ E4.0 man construction worker: medium-light skin tone +1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ E4.0 man construction worker: medium skin tone +1F477 1F3FD 200D 2642 ; minimally-qualified # 👷🏽‍♂ E4.0 man construction worker: medium skin tone +1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ E4.0 man construction worker: medium-dark skin tone +1F477 1F3FE 200D 2642 ; minimally-qualified # 👷🏾‍♂ E4.0 man construction worker: medium-dark skin tone +1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ E4.0 man construction worker: dark skin tone +1F477 1F3FF 200D 2642 ; minimally-qualified # 👷🏿‍♂ E4.0 man construction worker: dark skin tone +1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ E4.0 woman construction worker +1F477 200D 2640 ; minimally-qualified # 👷‍♀ E4.0 woman construction worker +1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ E4.0 woman construction worker: light skin tone +1F477 1F3FB 200D 2640 ; minimally-qualified # 👷🏻‍♀ E4.0 woman construction worker: light skin tone +1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ E4.0 woman construction worker: medium-light skin tone +1F477 1F3FC 200D 2640 ; minimally-qualified # 👷🏼‍♀ E4.0 woman construction worker: medium-light skin tone +1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ E4.0 woman construction worker: medium skin tone +1F477 1F3FD 200D 2640 ; minimally-qualified # 👷🏽‍♀ E4.0 woman construction worker: medium skin tone +1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ E4.0 woman construction worker: medium-dark skin tone +1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾‍♀ E4.0 woman construction worker: medium-dark skin tone +1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ E4.0 woman construction worker: dark skin tone +1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿‍♀ E4.0 woman construction worker: dark skin tone +1F934 ; fully-qualified # 🤴 E3.0 prince +1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone +1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone +1F934 1F3FD ; fully-qualified # 🤴🏽 E3.0 prince: medium skin tone +1F934 1F3FE ; fully-qualified # 🤴🏾 E3.0 prince: medium-dark skin tone +1F934 1F3FF ; fully-qualified # 🤴🏿 E3.0 prince: dark skin tone +1F478 ; fully-qualified # 👸 E0.6 princess +1F478 1F3FB ; fully-qualified # 👸🏻 E1.0 princess: light skin tone +1F478 1F3FC ; fully-qualified # 👸🏼 E1.0 princess: medium-light skin tone +1F478 1F3FD ; fully-qualified # 👸🏽 E1.0 princess: medium skin tone +1F478 1F3FE ; fully-qualified # 👸🏾 E1.0 princess: medium-dark skin tone +1F478 1F3FF ; fully-qualified # 👸🏿 E1.0 princess: dark skin tone +1F473 ; fully-qualified # 👳 E0.6 person wearing turban +1F473 1F3FB ; fully-qualified # 👳🏻 E1.0 person wearing turban: light skin tone +1F473 1F3FC ; fully-qualified # 👳🏼 E1.0 person wearing turban: medium-light skin tone +1F473 1F3FD ; fully-qualified # 👳🏽 E1.0 person wearing turban: medium skin tone +1F473 1F3FE ; fully-qualified # 👳🏾 E1.0 person wearing turban: medium-dark skin tone +1F473 1F3FF ; fully-qualified # 👳🏿 E1.0 person wearing turban: dark skin tone +1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ E4.0 man wearing turban +1F473 200D 2642 ; minimally-qualified # 👳‍♂ E4.0 man wearing turban +1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ E4.0 man wearing turban: light skin tone +1F473 1F3FB 200D 2642 ; minimally-qualified # 👳🏻‍♂ E4.0 man wearing turban: light skin tone +1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ E4.0 man wearing turban: medium-light skin tone +1F473 1F3FC 200D 2642 ; minimally-qualified # 👳🏼‍♂ E4.0 man wearing turban: medium-light skin tone +1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ E4.0 man wearing turban: medium skin tone +1F473 1F3FD 200D 2642 ; minimally-qualified # 👳🏽‍♂ E4.0 man wearing turban: medium skin tone +1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ E4.0 man wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2642 ; minimally-qualified # 👳🏾‍♂ E4.0 man wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ E4.0 man wearing turban: dark skin tone +1F473 1F3FF 200D 2642 ; minimally-qualified # 👳🏿‍♂ E4.0 man wearing turban: dark skin tone +1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ E4.0 woman wearing turban +1F473 200D 2640 ; minimally-qualified # 👳‍♀ E4.0 woman wearing turban +1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ E4.0 woman wearing turban: light skin tone +1F473 1F3FB 200D 2640 ; minimally-qualified # 👳🏻‍♀ E4.0 woman wearing turban: light skin tone +1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ E4.0 woman wearing turban: medium-light skin tone +1F473 1F3FC 200D 2640 ; minimally-qualified # 👳🏼‍♀ E4.0 woman wearing turban: medium-light skin tone +1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ E4.0 woman wearing turban: medium skin tone +1F473 1F3FD 200D 2640 ; minimally-qualified # 👳🏽‍♀ E4.0 woman wearing turban: medium skin tone +1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ E4.0 woman wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2640 ; minimally-qualified # 👳🏾‍♀ E4.0 woman wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ E4.0 woman wearing turban: dark skin tone +1F473 1F3FF 200D 2640 ; minimally-qualified # 👳🏿‍♀ E4.0 woman wearing turban: dark skin tone +1F472 ; fully-qualified # 👲 E0.6 man with skullcap +1F472 1F3FB ; fully-qualified # 👲🏻 E1.0 man with skullcap: light skin tone +1F472 1F3FC ; fully-qualified # 👲🏼 E1.0 man with skullcap: medium-light skin tone +1F472 1F3FD ; fully-qualified # 👲🏽 E1.0 man with skullcap: medium skin tone +1F472 1F3FE ; fully-qualified # 👲🏾 E1.0 man with skullcap: medium-dark skin tone +1F472 1F3FF ; fully-qualified # 👲🏿 E1.0 man with skullcap: dark skin tone +1F9D5 ; fully-qualified # 🧕 E5.0 woman with headscarf +1F9D5 1F3FB ; fully-qualified # 🧕🏻 E5.0 woman with headscarf: light skin tone +1F9D5 1F3FC ; fully-qualified # 🧕🏼 E5.0 woman with headscarf: medium-light skin tone +1F9D5 1F3FD ; fully-qualified # 🧕🏽 E5.0 woman with headscarf: medium skin tone +1F9D5 1F3FE ; fully-qualified # 🧕🏾 E5.0 woman with headscarf: medium-dark skin tone +1F9D5 1F3FF ; fully-qualified # 🧕🏿 E5.0 woman with headscarf: dark skin tone +1F935 ; fully-qualified # 🤵 E3.0 man in tuxedo +1F935 1F3FB ; fully-qualified # 🤵🏻 E3.0 man in tuxedo: light skin tone +1F935 1F3FC ; fully-qualified # 🤵🏼 E3.0 man in tuxedo: medium-light skin tone +1F935 1F3FD ; fully-qualified # 🤵🏽 E3.0 man in tuxedo: medium skin tone +1F935 1F3FE ; fully-qualified # 🤵🏾 E3.0 man in tuxedo: medium-dark skin tone +1F935 1F3FF ; fully-qualified # 🤵🏿 E3.0 man in tuxedo: dark skin tone +1F935 200D 2642 FE0F ; fully-qualified # 🤵‍♂️ E13.0 man in tuxedo +1F935 200D 2642 ; minimally-qualified # 🤵‍♂ E13.0 man in tuxedo +1F935 1F3FB 200D 2642 FE0F ; fully-qualified # 🤵🏻‍♂️ E13.0 man in tuxedo: light skin tone +1F935 1F3FB 200D 2642 ; minimally-qualified # 🤵🏻‍♂ E13.0 man in tuxedo: light skin tone +1F935 1F3FC 200D 2642 FE0F ; fully-qualified # 🤵🏼‍♂️ E13.0 man in tuxedo: medium-light skin tone +1F935 1F3FC 200D 2642 ; minimally-qualified # 🤵🏼‍♂ E13.0 man in tuxedo: medium-light skin tone +1F935 1F3FD 200D 2642 FE0F ; fully-qualified # 🤵🏽‍♂️ E13.0 man in tuxedo: medium skin tone +1F935 1F3FD 200D 2642 ; minimally-qualified # 🤵🏽‍♂ E13.0 man in tuxedo: medium skin tone +1F935 1F3FE 200D 2642 FE0F ; fully-qualified # 🤵🏾‍♂️ E13.0 man in tuxedo: medium-dark skin tone +1F935 1F3FE 200D 2642 ; minimally-qualified # 🤵🏾‍♂ E13.0 man in tuxedo: medium-dark skin tone +1F935 1F3FF 200D 2642 FE0F ; fully-qualified # 🤵🏿‍♂️ E13.0 man in tuxedo: dark skin tone +1F935 1F3FF 200D 2642 ; minimally-qualified # 🤵🏿‍♂ E13.0 man in tuxedo: dark skin tone +1F935 200D 2640 FE0F ; fully-qualified # 🤵‍♀️ E13.0 woman in tuxedo +1F935 200D 2640 ; minimally-qualified # 🤵‍♀ E13.0 woman in tuxedo +1F935 1F3FB 200D 2640 FE0F ; fully-qualified # 🤵🏻‍♀️ E13.0 woman in tuxedo: light skin tone +1F935 1F3FB 200D 2640 ; minimally-qualified # 🤵🏻‍♀ E13.0 woman in tuxedo: light skin tone +1F935 1F3FC 200D 2640 FE0F ; fully-qualified # 🤵🏼‍♀️ E13.0 woman in tuxedo: medium-light skin tone +1F935 1F3FC 200D 2640 ; minimally-qualified # 🤵🏼‍♀ E13.0 woman in tuxedo: medium-light skin tone +1F935 1F3FD 200D 2640 FE0F ; fully-qualified # 🤵🏽‍♀️ E13.0 woman in tuxedo: medium skin tone +1F935 1F3FD 200D 2640 ; minimally-qualified # 🤵🏽‍♀ E13.0 woman in tuxedo: medium skin tone +1F935 1F3FE 200D 2640 FE0F ; fully-qualified # 🤵🏾‍♀️ E13.0 woman in tuxedo: medium-dark skin tone +1F935 1F3FE 200D 2640 ; minimally-qualified # 🤵🏾‍♀ E13.0 woman in tuxedo: medium-dark skin tone +1F935 1F3FF 200D 2640 FE0F ; fully-qualified # 🤵🏿‍♀️ E13.0 woman in tuxedo: dark skin tone +1F935 1F3FF 200D 2640 ; minimally-qualified # 🤵🏿‍♀ E13.0 woman in tuxedo: dark skin tone +1F470 ; fully-qualified # 👰 E0.6 bride with veil +1F470 1F3FB ; fully-qualified # 👰🏻 E1.0 bride with veil: light skin tone +1F470 1F3FC ; fully-qualified # 👰🏼 E1.0 bride with veil: medium-light skin tone +1F470 1F3FD ; fully-qualified # 👰🏽 E1.0 bride with veil: medium skin tone +1F470 1F3FE ; fully-qualified # 👰🏾 E1.0 bride with veil: medium-dark skin tone +1F470 1F3FF ; fully-qualified # 👰🏿 E1.0 bride with veil: dark skin tone +1F470 200D 2642 FE0F ; fully-qualified # 👰‍♂️ E13.0 man with veil +1F470 200D 2642 ; minimally-qualified # 👰‍♂ E13.0 man with veil +1F470 1F3FB 200D 2642 FE0F ; fully-qualified # 👰🏻‍♂️ E13.0 man with veil: light skin tone +1F470 1F3FB 200D 2642 ; minimally-qualified # 👰🏻‍♂ E13.0 man with veil: light skin tone +1F470 1F3FC 200D 2642 FE0F ; fully-qualified # 👰🏼‍♂️ E13.0 man with veil: medium-light skin tone +1F470 1F3FC 200D 2642 ; minimally-qualified # 👰🏼‍♂ E13.0 man with veil: medium-light skin tone +1F470 1F3FD 200D 2642 FE0F ; fully-qualified # 👰🏽‍♂️ E13.0 man with veil: medium skin tone +1F470 1F3FD 200D 2642 ; minimally-qualified # 👰🏽‍♂ E13.0 man with veil: medium skin tone +1F470 1F3FE 200D 2642 FE0F ; fully-qualified # 👰🏾‍♂️ E13.0 man with veil: medium-dark skin tone +1F470 1F3FE 200D 2642 ; minimally-qualified # 👰🏾‍♂ E13.0 man with veil: medium-dark skin tone +1F470 1F3FF 200D 2642 FE0F ; fully-qualified # 👰🏿‍♂️ E13.0 man with veil: dark skin tone +1F470 1F3FF 200D 2642 ; minimally-qualified # 👰🏿‍♂ E13.0 man with veil: dark skin tone +1F470 200D 2640 FE0F ; fully-qualified # 👰‍♀️ E13.0 woman with veil +1F470 200D 2640 ; minimally-qualified # 👰‍♀ E13.0 woman with veil +1F470 1F3FB 200D 2640 FE0F ; fully-qualified # 👰🏻‍♀️ E13.0 woman with veil: light skin tone +1F470 1F3FB 200D 2640 ; minimally-qualified # 👰🏻‍♀ E13.0 woman with veil: light skin tone +1F470 1F3FC 200D 2640 FE0F ; fully-qualified # 👰🏼‍♀️ E13.0 woman with veil: medium-light skin tone +1F470 1F3FC 200D 2640 ; minimally-qualified # 👰🏼‍♀ E13.0 woman with veil: medium-light skin tone +1F470 1F3FD 200D 2640 FE0F ; fully-qualified # 👰🏽‍♀️ E13.0 woman with veil: medium skin tone +1F470 1F3FD 200D 2640 ; minimally-qualified # 👰🏽‍♀ E13.0 woman with veil: medium skin tone +1F470 1F3FE 200D 2640 FE0F ; fully-qualified # 👰🏾‍♀️ E13.0 woman with veil: medium-dark skin tone +1F470 1F3FE 200D 2640 ; minimally-qualified # 👰🏾‍♀ E13.0 woman with veil: medium-dark skin tone +1F470 1F3FF 200D 2640 FE0F ; fully-qualified # 👰🏿‍♀️ E13.0 woman with veil: dark skin tone +1F470 1F3FF 200D 2640 ; minimally-qualified # 👰🏿‍♀ E13.0 woman with veil: dark skin tone +1F930 ; fully-qualified # 🤰 E3.0 pregnant woman +1F930 1F3FB ; fully-qualified # 🤰🏻 E3.0 pregnant woman: light skin tone +1F930 1F3FC ; fully-qualified # 🤰🏼 E3.0 pregnant woman: medium-light skin tone +1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone +1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone +1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone +1F931 ; fully-qualified # 🤱 E5.0 breast-feeding +1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone +1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone +1F931 1F3FD ; fully-qualified # 🤱🏽 E5.0 breast-feeding: medium skin tone +1F931 1F3FE ; fully-qualified # 🤱🏾 E5.0 breast-feeding: medium-dark skin tone +1F931 1F3FF ; fully-qualified # 🤱🏿 E5.0 breast-feeding: dark skin tone +1F469 200D 1F37C ; fully-qualified # 👩‍🍼 E13.0 woman feeding baby +1F469 1F3FB 200D 1F37C ; fully-qualified # 👩🏻‍🍼 E13.0 woman feeding baby: light skin tone +1F469 1F3FC 200D 1F37C ; fully-qualified # 👩🏼‍🍼 E13.0 woman feeding baby: medium-light skin tone +1F469 1F3FD 200D 1F37C ; fully-qualified # 👩🏽‍🍼 E13.0 woman feeding baby: medium skin tone +1F469 1F3FE 200D 1F37C ; fully-qualified # 👩🏾‍🍼 E13.0 woman feeding baby: medium-dark skin tone +1F469 1F3FF 200D 1F37C ; fully-qualified # 👩🏿‍🍼 E13.0 woman feeding baby: dark skin tone +1F468 200D 1F37C ; fully-qualified # 👨‍🍼 E13.0 man feeding baby +1F468 1F3FB 200D 1F37C ; fully-qualified # 👨🏻‍🍼 E13.0 man feeding baby: light skin tone +1F468 1F3FC 200D 1F37C ; fully-qualified # 👨🏼‍🍼 E13.0 man feeding baby: medium-light skin tone +1F468 1F3FD 200D 1F37C ; fully-qualified # 👨🏽‍🍼 E13.0 man feeding baby: medium skin tone +1F468 1F3FE 200D 1F37C ; fully-qualified # 👨🏾‍🍼 E13.0 man feeding baby: medium-dark skin tone +1F468 1F3FF 200D 1F37C ; fully-qualified # 👨🏿‍🍼 E13.0 man feeding baby: dark skin tone +1F9D1 200D 1F37C ; fully-qualified # 🧑‍🍼 E13.0 person feeding baby +1F9D1 1F3FB 200D 1F37C ; fully-qualified # 🧑🏻‍🍼 E13.0 person feeding baby: light skin tone +1F9D1 1F3FC 200D 1F37C ; fully-qualified # 🧑🏼‍🍼 E13.0 person feeding baby: medium-light skin tone +1F9D1 1F3FD 200D 1F37C ; fully-qualified # 🧑🏽‍🍼 E13.0 person feeding baby: medium skin tone +1F9D1 1F3FE 200D 1F37C ; fully-qualified # 🧑🏾‍🍼 E13.0 person feeding baby: medium-dark skin tone +1F9D1 1F3FF 200D 1F37C ; fully-qualified # 🧑🏿‍🍼 E13.0 person feeding baby: dark skin tone + +# subgroup: person-fantasy +1F47C ; fully-qualified # 👼 E0.6 baby angel +1F47C 1F3FB ; fully-qualified # 👼🏻 E1.0 baby angel: light skin tone +1F47C 1F3FC ; fully-qualified # 👼🏼 E1.0 baby angel: medium-light skin tone +1F47C 1F3FD ; fully-qualified # 👼🏽 E1.0 baby angel: medium skin tone +1F47C 1F3FE ; fully-qualified # 👼🏾 E1.0 baby angel: medium-dark skin tone +1F47C 1F3FF ; fully-qualified # 👼🏿 E1.0 baby angel: dark skin tone +1F385 ; fully-qualified # 🎅 E0.6 Santa Claus +1F385 1F3FB ; fully-qualified # 🎅🏻 E1.0 Santa Claus: light skin tone +1F385 1F3FC ; fully-qualified # 🎅🏼 E1.0 Santa Claus: medium-light skin tone +1F385 1F3FD ; fully-qualified # 🎅🏽 E1.0 Santa Claus: medium skin tone +1F385 1F3FE ; fully-qualified # 🎅🏾 E1.0 Santa Claus: medium-dark skin tone +1F385 1F3FF ; fully-qualified # 🎅🏿 E1.0 Santa Claus: dark skin tone +1F936 ; fully-qualified # 🤶 E3.0 Mrs. Claus +1F936 1F3FB ; fully-qualified # 🤶🏻 E3.0 Mrs. Claus: light skin tone +1F936 1F3FC ; fully-qualified # 🤶🏼 E3.0 Mrs. Claus: medium-light skin tone +1F936 1F3FD ; fully-qualified # 🤶🏽 E3.0 Mrs. Claus: medium skin tone +1F936 1F3FE ; fully-qualified # 🤶🏾 E3.0 Mrs. Claus: medium-dark skin tone +1F936 1F3FF ; fully-qualified # 🤶🏿 E3.0 Mrs. Claus: dark skin tone +1F9D1 200D 1F384 ; fully-qualified # 🧑‍🎄 E13.0 mx claus +1F9D1 1F3FB 200D 1F384 ; fully-qualified # 🧑🏻‍🎄 E13.0 mx claus: light skin tone +1F9D1 1F3FC 200D 1F384 ; fully-qualified # 🧑🏼‍🎄 E13.0 mx claus: medium-light skin tone +1F9D1 1F3FD 200D 1F384 ; fully-qualified # 🧑🏽‍🎄 E13.0 mx claus: medium skin tone +1F9D1 1F3FE 200D 1F384 ; fully-qualified # 🧑🏾‍🎄 E13.0 mx claus: medium-dark skin tone +1F9D1 1F3FF 200D 1F384 ; fully-qualified # 🧑🏿‍🎄 E13.0 mx claus: dark skin tone +1F9B8 ; fully-qualified # 🦸 E11.0 superhero +1F9B8 1F3FB ; fully-qualified # 🦸🏻 E11.0 superhero: light skin tone +1F9B8 1F3FC ; fully-qualified # 🦸🏼 E11.0 superhero: medium-light skin tone +1F9B8 1F3FD ; fully-qualified # 🦸🏽 E11.0 superhero: medium skin tone +1F9B8 1F3FE ; fully-qualified # 🦸🏾 E11.0 superhero: medium-dark skin tone +1F9B8 1F3FF ; fully-qualified # 🦸🏿 E11.0 superhero: dark skin tone +1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ E11.0 man superhero +1F9B8 200D 2642 ; minimally-qualified # 🦸‍♂ E11.0 man superhero +1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ E11.0 man superhero: light skin tone +1F9B8 1F3FB 200D 2642 ; minimally-qualified # 🦸🏻‍♂ E11.0 man superhero: light skin tone +1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ E11.0 man superhero: medium-light skin tone +1F9B8 1F3FC 200D 2642 ; minimally-qualified # 🦸🏼‍♂ E11.0 man superhero: medium-light skin tone +1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ E11.0 man superhero: medium skin tone +1F9B8 1F3FD 200D 2642 ; minimally-qualified # 🦸🏽‍♂ E11.0 man superhero: medium skin tone +1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ E11.0 man superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2642 ; minimally-qualified # 🦸🏾‍♂ E11.0 man superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ E11.0 man superhero: dark skin tone +1F9B8 1F3FF 200D 2642 ; minimally-qualified # 🦸🏿‍♂ E11.0 man superhero: dark skin tone +1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ E11.0 woman superhero +1F9B8 200D 2640 ; minimally-qualified # 🦸‍♀ E11.0 woman superhero +1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ E11.0 woman superhero: light skin tone +1F9B8 1F3FB 200D 2640 ; minimally-qualified # 🦸🏻‍♀ E11.0 woman superhero: light skin tone +1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ E11.0 woman superhero: medium-light skin tone +1F9B8 1F3FC 200D 2640 ; minimally-qualified # 🦸🏼‍♀ E11.0 woman superhero: medium-light skin tone +1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ E11.0 woman superhero: medium skin tone +1F9B8 1F3FD 200D 2640 ; minimally-qualified # 🦸🏽‍♀ E11.0 woman superhero: medium skin tone +1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ E11.0 woman superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2640 ; minimally-qualified # 🦸🏾‍♀ E11.0 woman superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ E11.0 woman superhero: dark skin tone +1F9B8 1F3FF 200D 2640 ; minimally-qualified # 🦸🏿‍♀ E11.0 woman superhero: dark skin tone +1F9B9 ; fully-qualified # 🦹 E11.0 supervillain +1F9B9 1F3FB ; fully-qualified # 🦹🏻 E11.0 supervillain: light skin tone +1F9B9 1F3FC ; fully-qualified # 🦹🏼 E11.0 supervillain: medium-light skin tone +1F9B9 1F3FD ; fully-qualified # 🦹🏽 E11.0 supervillain: medium skin tone +1F9B9 1F3FE ; fully-qualified # 🦹🏾 E11.0 supervillain: medium-dark skin tone +1F9B9 1F3FF ; fully-qualified # 🦹🏿 E11.0 supervillain: dark skin tone +1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ E11.0 man supervillain +1F9B9 200D 2642 ; minimally-qualified # 🦹‍♂ E11.0 man supervillain +1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ E11.0 man supervillain: light skin tone +1F9B9 1F3FB 200D 2642 ; minimally-qualified # 🦹🏻‍♂ E11.0 man supervillain: light skin tone +1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ E11.0 man supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2642 ; minimally-qualified # 🦹🏼‍♂ E11.0 man supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ E11.0 man supervillain: medium skin tone +1F9B9 1F3FD 200D 2642 ; minimally-qualified # 🦹🏽‍♂ E11.0 man supervillain: medium skin tone +1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ E11.0 man supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2642 ; minimally-qualified # 🦹🏾‍♂ E11.0 man supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ E11.0 man supervillain: dark skin tone +1F9B9 1F3FF 200D 2642 ; minimally-qualified # 🦹🏿‍♂ E11.0 man supervillain: dark skin tone +1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ E11.0 woman supervillain +1F9B9 200D 2640 ; minimally-qualified # 🦹‍♀ E11.0 woman supervillain +1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ E11.0 woman supervillain: light skin tone +1F9B9 1F3FB 200D 2640 ; minimally-qualified # 🦹🏻‍♀ E11.0 woman supervillain: light skin tone +1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ E11.0 woman supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2640 ; minimally-qualified # 🦹🏼‍♀ E11.0 woman supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ E11.0 woman supervillain: medium skin tone +1F9B9 1F3FD 200D 2640 ; minimally-qualified # 🦹🏽‍♀ E11.0 woman supervillain: medium skin tone +1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ E11.0 woman supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2640 ; minimally-qualified # 🦹🏾‍♀ E11.0 woman supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ E11.0 woman supervillain: dark skin tone +1F9B9 1F3FF 200D 2640 ; minimally-qualified # 🦹🏿‍♀ E11.0 woman supervillain: dark skin tone +1F9D9 ; fully-qualified # 🧙 E5.0 mage +1F9D9 1F3FB ; fully-qualified # 🧙🏻 E5.0 mage: light skin tone +1F9D9 1F3FC ; fully-qualified # 🧙🏼 E5.0 mage: medium-light skin tone +1F9D9 1F3FD ; fully-qualified # 🧙🏽 E5.0 mage: medium skin tone +1F9D9 1F3FE ; fully-qualified # 🧙🏾 E5.0 mage: medium-dark skin tone +1F9D9 1F3FF ; fully-qualified # 🧙🏿 E5.0 mage: dark skin tone +1F9D9 200D 2642 FE0F ; fully-qualified # 🧙‍♂️ E5.0 man mage +1F9D9 200D 2642 ; minimally-qualified # 🧙‍♂ E5.0 man mage +1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻‍♂️ E5.0 man mage: light skin tone +1F9D9 1F3FB 200D 2642 ; minimally-qualified # 🧙🏻‍♂ E5.0 man mage: light skin tone +1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼‍♂️ E5.0 man mage: medium-light skin tone +1F9D9 1F3FC 200D 2642 ; minimally-qualified # 🧙🏼‍♂ E5.0 man mage: medium-light skin tone +1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽‍♂️ E5.0 man mage: medium skin tone +1F9D9 1F3FD 200D 2642 ; minimally-qualified # 🧙🏽‍♂ E5.0 man mage: medium skin tone +1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾‍♂️ E5.0 man mage: medium-dark skin tone +1F9D9 1F3FE 200D 2642 ; minimally-qualified # 🧙🏾‍♂ E5.0 man mage: medium-dark skin tone +1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿‍♂️ E5.0 man mage: dark skin tone +1F9D9 1F3FF 200D 2642 ; minimally-qualified # 🧙🏿‍♂ E5.0 man mage: dark skin tone +1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ E5.0 woman mage +1F9D9 200D 2640 ; minimally-qualified # 🧙‍♀ E5.0 woman mage +1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ E5.0 woman mage: light skin tone +1F9D9 1F3FB 200D 2640 ; minimally-qualified # 🧙🏻‍♀ E5.0 woman mage: light skin tone +1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ E5.0 woman mage: medium-light skin tone +1F9D9 1F3FC 200D 2640 ; minimally-qualified # 🧙🏼‍♀ E5.0 woman mage: medium-light skin tone +1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽‍♀️ E5.0 woman mage: medium skin tone +1F9D9 1F3FD 200D 2640 ; minimally-qualified # 🧙🏽‍♀ E5.0 woman mage: medium skin tone +1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾‍♀️ E5.0 woman mage: medium-dark skin tone +1F9D9 1F3FE 200D 2640 ; minimally-qualified # 🧙🏾‍♀ E5.0 woman mage: medium-dark skin tone +1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿‍♀️ E5.0 woman mage: dark skin tone +1F9D9 1F3FF 200D 2640 ; minimally-qualified # 🧙🏿‍♀ E5.0 woman mage: dark skin tone +1F9DA ; fully-qualified # 🧚 E5.0 fairy +1F9DA 1F3FB ; fully-qualified # 🧚🏻 E5.0 fairy: light skin tone +1F9DA 1F3FC ; fully-qualified # 🧚🏼 E5.0 fairy: medium-light skin tone +1F9DA 1F3FD ; fully-qualified # 🧚🏽 E5.0 fairy: medium skin tone +1F9DA 1F3FE ; fully-qualified # 🧚🏾 E5.0 fairy: medium-dark skin tone +1F9DA 1F3FF ; fully-qualified # 🧚🏿 E5.0 fairy: dark skin tone +1F9DA 200D 2642 FE0F ; fully-qualified # 🧚‍♂️ E5.0 man fairy +1F9DA 200D 2642 ; minimally-qualified # 🧚‍♂ E5.0 man fairy +1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻‍♂️ E5.0 man fairy: light skin tone +1F9DA 1F3FB 200D 2642 ; minimally-qualified # 🧚🏻‍♂ E5.0 man fairy: light skin tone +1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼‍♂️ E5.0 man fairy: medium-light skin tone +1F9DA 1F3FC 200D 2642 ; minimally-qualified # 🧚🏼‍♂ E5.0 man fairy: medium-light skin tone +1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽‍♂️ E5.0 man fairy: medium skin tone +1F9DA 1F3FD 200D 2642 ; minimally-qualified # 🧚🏽‍♂ E5.0 man fairy: medium skin tone +1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾‍♂️ E5.0 man fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2642 ; minimally-qualified # 🧚🏾‍♂ E5.0 man fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿‍♂️ E5.0 man fairy: dark skin tone +1F9DA 1F3FF 200D 2642 ; minimally-qualified # 🧚🏿‍♂ E5.0 man fairy: dark skin tone +1F9DA 200D 2640 FE0F ; fully-qualified # 🧚‍♀️ E5.0 woman fairy +1F9DA 200D 2640 ; minimally-qualified # 🧚‍♀ E5.0 woman fairy +1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻‍♀️ E5.0 woman fairy: light skin tone +1F9DA 1F3FB 200D 2640 ; minimally-qualified # 🧚🏻‍♀ E5.0 woman fairy: light skin tone +1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼‍♀️ E5.0 woman fairy: medium-light skin tone +1F9DA 1F3FC 200D 2640 ; minimally-qualified # 🧚🏼‍♀ E5.0 woman fairy: medium-light skin tone +1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽‍♀️ E5.0 woman fairy: medium skin tone +1F9DA 1F3FD 200D 2640 ; minimally-qualified # 🧚🏽‍♀ E5.0 woman fairy: medium skin tone +1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾‍♀️ E5.0 woman fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2640 ; minimally-qualified # 🧚🏾‍♀ E5.0 woman fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿‍♀️ E5.0 woman fairy: dark skin tone +1F9DA 1F3FF 200D 2640 ; minimally-qualified # 🧚🏿‍♀ E5.0 woman fairy: dark skin tone +1F9DB ; fully-qualified # 🧛 E5.0 vampire +1F9DB 1F3FB ; fully-qualified # 🧛🏻 E5.0 vampire: light skin tone +1F9DB 1F3FC ; fully-qualified # 🧛🏼 E5.0 vampire: medium-light skin tone +1F9DB 1F3FD ; fully-qualified # 🧛🏽 E5.0 vampire: medium skin tone +1F9DB 1F3FE ; fully-qualified # 🧛🏾 E5.0 vampire: medium-dark skin tone +1F9DB 1F3FF ; fully-qualified # 🧛🏿 E5.0 vampire: dark skin tone +1F9DB 200D 2642 FE0F ; fully-qualified # 🧛‍♂️ E5.0 man vampire +1F9DB 200D 2642 ; minimally-qualified # 🧛‍♂ E5.0 man vampire +1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻‍♂️ E5.0 man vampire: light skin tone +1F9DB 1F3FB 200D 2642 ; minimally-qualified # 🧛🏻‍♂ E5.0 man vampire: light skin tone +1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼‍♂️ E5.0 man vampire: medium-light skin tone +1F9DB 1F3FC 200D 2642 ; minimally-qualified # 🧛🏼‍♂ E5.0 man vampire: medium-light skin tone +1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽‍♂️ E5.0 man vampire: medium skin tone +1F9DB 1F3FD 200D 2642 ; minimally-qualified # 🧛🏽‍♂ E5.0 man vampire: medium skin tone +1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾‍♂️ E5.0 man vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2642 ; minimally-qualified # 🧛🏾‍♂ E5.0 man vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿‍♂️ E5.0 man vampire: dark skin tone +1F9DB 1F3FF 200D 2642 ; minimally-qualified # 🧛🏿‍♂ E5.0 man vampire: dark skin tone +1F9DB 200D 2640 FE0F ; fully-qualified # 🧛‍♀️ E5.0 woman vampire +1F9DB 200D 2640 ; minimally-qualified # 🧛‍♀ E5.0 woman vampire +1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻‍♀️ E5.0 woman vampire: light skin tone +1F9DB 1F3FB 200D 2640 ; minimally-qualified # 🧛🏻‍♀ E5.0 woman vampire: light skin tone +1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼‍♀️ E5.0 woman vampire: medium-light skin tone +1F9DB 1F3FC 200D 2640 ; minimally-qualified # 🧛🏼‍♀ E5.0 woman vampire: medium-light skin tone +1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽‍♀️ E5.0 woman vampire: medium skin tone +1F9DB 1F3FD 200D 2640 ; minimally-qualified # 🧛🏽‍♀ E5.0 woman vampire: medium skin tone +1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾‍♀️ E5.0 woman vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2640 ; minimally-qualified # 🧛🏾‍♀ E5.0 woman vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿‍♀️ E5.0 woman vampire: dark skin tone +1F9DB 1F3FF 200D 2640 ; minimally-qualified # 🧛🏿‍♀ E5.0 woman vampire: dark skin tone +1F9DC ; fully-qualified # 🧜 E5.0 merperson +1F9DC 1F3FB ; fully-qualified # 🧜🏻 E5.0 merperson: light skin tone +1F9DC 1F3FC ; fully-qualified # 🧜🏼 E5.0 merperson: medium-light skin tone +1F9DC 1F3FD ; fully-qualified # 🧜🏽 E5.0 merperson: medium skin tone +1F9DC 1F3FE ; fully-qualified # 🧜🏾 E5.0 merperson: medium-dark skin tone +1F9DC 1F3FF ; fully-qualified # 🧜🏿 E5.0 merperson: dark skin tone +1F9DC 200D 2642 FE0F ; fully-qualified # 🧜‍♂️ E5.0 merman +1F9DC 200D 2642 ; minimally-qualified # 🧜‍♂ E5.0 merman +1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻‍♂️ E5.0 merman: light skin tone +1F9DC 1F3FB 200D 2642 ; minimally-qualified # 🧜🏻‍♂ E5.0 merman: light skin tone +1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼‍♂️ E5.0 merman: medium-light skin tone +1F9DC 1F3FC 200D 2642 ; minimally-qualified # 🧜🏼‍♂ E5.0 merman: medium-light skin tone +1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽‍♂️ E5.0 merman: medium skin tone +1F9DC 1F3FD 200D 2642 ; minimally-qualified # 🧜🏽‍♂ E5.0 merman: medium skin tone +1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾‍♂️ E5.0 merman: medium-dark skin tone +1F9DC 1F3FE 200D 2642 ; minimally-qualified # 🧜🏾‍♂ E5.0 merman: medium-dark skin tone +1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿‍♂️ E5.0 merman: dark skin tone +1F9DC 1F3FF 200D 2642 ; minimally-qualified # 🧜🏿‍♂ E5.0 merman: dark skin tone +1F9DC 200D 2640 FE0F ; fully-qualified # 🧜‍♀️ E5.0 mermaid +1F9DC 200D 2640 ; minimally-qualified # 🧜‍♀ E5.0 mermaid +1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻‍♀️ E5.0 mermaid: light skin tone +1F9DC 1F3FB 200D 2640 ; minimally-qualified # 🧜🏻‍♀ E5.0 mermaid: light skin tone +1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼‍♀️ E5.0 mermaid: medium-light skin tone +1F9DC 1F3FC 200D 2640 ; minimally-qualified # 🧜🏼‍♀ E5.0 mermaid: medium-light skin tone +1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽‍♀️ E5.0 mermaid: medium skin tone +1F9DC 1F3FD 200D 2640 ; minimally-qualified # 🧜🏽‍♀ E5.0 mermaid: medium skin tone +1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾‍♀️ E5.0 mermaid: medium-dark skin tone +1F9DC 1F3FE 200D 2640 ; minimally-qualified # 🧜🏾‍♀ E5.0 mermaid: medium-dark skin tone +1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿‍♀️ E5.0 mermaid: dark skin tone +1F9DC 1F3FF 200D 2640 ; minimally-qualified # 🧜🏿‍♀ E5.0 mermaid: dark skin tone +1F9DD ; fully-qualified # 🧝 E5.0 elf +1F9DD 1F3FB ; fully-qualified # 🧝🏻 E5.0 elf: light skin tone +1F9DD 1F3FC ; fully-qualified # 🧝🏼 E5.0 elf: medium-light skin tone +1F9DD 1F3FD ; fully-qualified # 🧝🏽 E5.0 elf: medium skin tone +1F9DD 1F3FE ; fully-qualified # 🧝🏾 E5.0 elf: medium-dark skin tone +1F9DD 1F3FF ; fully-qualified # 🧝🏿 E5.0 elf: dark skin tone +1F9DD 200D 2642 FE0F ; fully-qualified # 🧝‍♂️ E5.0 man elf +1F9DD 200D 2642 ; minimally-qualified # 🧝‍♂ E5.0 man elf +1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻‍♂️ E5.0 man elf: light skin tone +1F9DD 1F3FB 200D 2642 ; minimally-qualified # 🧝🏻‍♂ E5.0 man elf: light skin tone +1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼‍♂️ E5.0 man elf: medium-light skin tone +1F9DD 1F3FC 200D 2642 ; minimally-qualified # 🧝🏼‍♂ E5.0 man elf: medium-light skin tone +1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽‍♂️ E5.0 man elf: medium skin tone +1F9DD 1F3FD 200D 2642 ; minimally-qualified # 🧝🏽‍♂ E5.0 man elf: medium skin tone +1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾‍♂️ E5.0 man elf: medium-dark skin tone +1F9DD 1F3FE 200D 2642 ; minimally-qualified # 🧝🏾‍♂ E5.0 man elf: medium-dark skin tone +1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿‍♂️ E5.0 man elf: dark skin tone +1F9DD 1F3FF 200D 2642 ; minimally-qualified # 🧝🏿‍♂ E5.0 man elf: dark skin tone +1F9DD 200D 2640 FE0F ; fully-qualified # 🧝‍♀️ E5.0 woman elf +1F9DD 200D 2640 ; minimally-qualified # 🧝‍♀ E5.0 woman elf +1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻‍♀️ E5.0 woman elf: light skin tone +1F9DD 1F3FB 200D 2640 ; minimally-qualified # 🧝🏻‍♀ E5.0 woman elf: light skin tone +1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼‍♀️ E5.0 woman elf: medium-light skin tone +1F9DD 1F3FC 200D 2640 ; minimally-qualified # 🧝🏼‍♀ E5.0 woman elf: medium-light skin tone +1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽‍♀️ E5.0 woman elf: medium skin tone +1F9DD 1F3FD 200D 2640 ; minimally-qualified # 🧝🏽‍♀ E5.0 woman elf: medium skin tone +1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾‍♀️ E5.0 woman elf: medium-dark skin tone +1F9DD 1F3FE 200D 2640 ; minimally-qualified # 🧝🏾‍♀ E5.0 woman elf: medium-dark skin tone +1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿‍♀️ E5.0 woman elf: dark skin tone +1F9DD 1F3FF 200D 2640 ; minimally-qualified # 🧝🏿‍♀ E5.0 woman elf: dark skin tone +1F9DE ; fully-qualified # 🧞 E5.0 genie +1F9DE 200D 2642 FE0F ; fully-qualified # 🧞‍♂️ E5.0 man genie +1F9DE 200D 2642 ; minimally-qualified # 🧞‍♂ E5.0 man genie +1F9DE 200D 2640 FE0F ; fully-qualified # 🧞‍♀️ E5.0 woman genie +1F9DE 200D 2640 ; minimally-qualified # 🧞‍♀ E5.0 woman genie +1F9DF ; fully-qualified # 🧟 E5.0 zombie +1F9DF 200D 2642 FE0F ; fully-qualified # 🧟‍♂️ E5.0 man zombie +1F9DF 200D 2642 ; minimally-qualified # 🧟‍♂ E5.0 man zombie +1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ E5.0 woman zombie +1F9DF 200D 2640 ; minimally-qualified # 🧟‍♀ E5.0 woman zombie + +# subgroup: person-activity +1F486 ; fully-qualified # 💆 E0.6 person getting massage +1F486 1F3FB ; fully-qualified # 💆🏻 E1.0 person getting massage: light skin tone +1F486 1F3FC ; fully-qualified # 💆🏼 E1.0 person getting massage: medium-light skin tone +1F486 1F3FD ; fully-qualified # 💆🏽 E1.0 person getting massage: medium skin tone +1F486 1F3FE ; fully-qualified # 💆🏾 E1.0 person getting massage: medium-dark skin tone +1F486 1F3FF ; fully-qualified # 💆🏿 E1.0 person getting massage: dark skin tone +1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ E4.0 man getting massage +1F486 200D 2642 ; minimally-qualified # 💆‍♂ E4.0 man getting massage +1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ E4.0 man getting massage: light skin tone +1F486 1F3FB 200D 2642 ; minimally-qualified # 💆🏻‍♂ E4.0 man getting massage: light skin tone +1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ E4.0 man getting massage: medium-light skin tone +1F486 1F3FC 200D 2642 ; minimally-qualified # 💆🏼‍♂ E4.0 man getting massage: medium-light skin tone +1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ E4.0 man getting massage: medium skin tone +1F486 1F3FD 200D 2642 ; minimally-qualified # 💆🏽‍♂ E4.0 man getting massage: medium skin tone +1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ E4.0 man getting massage: medium-dark skin tone +1F486 1F3FE 200D 2642 ; minimally-qualified # 💆🏾‍♂ E4.0 man getting massage: medium-dark skin tone +1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ E4.0 man getting massage: dark skin tone +1F486 1F3FF 200D 2642 ; minimally-qualified # 💆🏿‍♂ E4.0 man getting massage: dark skin tone +1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ E4.0 woman getting massage +1F486 200D 2640 ; minimally-qualified # 💆‍♀ E4.0 woman getting massage +1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ E4.0 woman getting massage: light skin tone +1F486 1F3FB 200D 2640 ; minimally-qualified # 💆🏻‍♀ E4.0 woman getting massage: light skin tone +1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ E4.0 woman getting massage: medium-light skin tone +1F486 1F3FC 200D 2640 ; minimally-qualified # 💆🏼‍♀ E4.0 woman getting massage: medium-light skin tone +1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ E4.0 woman getting massage: medium skin tone +1F486 1F3FD 200D 2640 ; minimally-qualified # 💆🏽‍♀ E4.0 woman getting massage: medium skin tone +1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ E4.0 woman getting massage: medium-dark skin tone +1F486 1F3FE 200D 2640 ; minimally-qualified # 💆🏾‍♀ E4.0 woman getting massage: medium-dark skin tone +1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ E4.0 woman getting massage: dark skin tone +1F486 1F3FF 200D 2640 ; minimally-qualified # 💆🏿‍♀ E4.0 woman getting massage: dark skin tone +1F487 ; fully-qualified # 💇 E0.6 person getting haircut +1F487 1F3FB ; fully-qualified # 💇🏻 E1.0 person getting haircut: light skin tone +1F487 1F3FC ; fully-qualified # 💇🏼 E1.0 person getting haircut: medium-light skin tone +1F487 1F3FD ; fully-qualified # 💇🏽 E1.0 person getting haircut: medium skin tone +1F487 1F3FE ; fully-qualified # 💇🏾 E1.0 person getting haircut: medium-dark skin tone +1F487 1F3FF ; fully-qualified # 💇🏿 E1.0 person getting haircut: dark skin tone +1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ E4.0 man getting haircut +1F487 200D 2642 ; minimally-qualified # 💇‍♂ E4.0 man getting haircut +1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ E4.0 man getting haircut: light skin tone +1F487 1F3FB 200D 2642 ; minimally-qualified # 💇🏻‍♂ E4.0 man getting haircut: light skin tone +1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ E4.0 man getting haircut: medium-light skin tone +1F487 1F3FC 200D 2642 ; minimally-qualified # 💇🏼‍♂ E4.0 man getting haircut: medium-light skin tone +1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ E4.0 man getting haircut: medium skin tone +1F487 1F3FD 200D 2642 ; minimally-qualified # 💇🏽‍♂ E4.0 man getting haircut: medium skin tone +1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ E4.0 man getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2642 ; minimally-qualified # 💇🏾‍♂ E4.0 man getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ E4.0 man getting haircut: dark skin tone +1F487 1F3FF 200D 2642 ; minimally-qualified # 💇🏿‍♂ E4.0 man getting haircut: dark skin tone +1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ E4.0 woman getting haircut +1F487 200D 2640 ; minimally-qualified # 💇‍♀ E4.0 woman getting haircut +1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ E4.0 woman getting haircut: light skin tone +1F487 1F3FB 200D 2640 ; minimally-qualified # 💇🏻‍♀ E4.0 woman getting haircut: light skin tone +1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ E4.0 woman getting haircut: medium-light skin tone +1F487 1F3FC 200D 2640 ; minimally-qualified # 💇🏼‍♀ E4.0 woman getting haircut: medium-light skin tone +1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ E4.0 woman getting haircut: medium skin tone +1F487 1F3FD 200D 2640 ; minimally-qualified # 💇🏽‍♀ E4.0 woman getting haircut: medium skin tone +1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ E4.0 woman getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2640 ; minimally-qualified # 💇🏾‍♀ E4.0 woman getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ E4.0 woman getting haircut: dark skin tone +1F487 1F3FF 200D 2640 ; minimally-qualified # 💇🏿‍♀ E4.0 woman getting haircut: dark skin tone +1F6B6 ; fully-qualified # 🚶 E0.6 person walking +1F6B6 1F3FB ; fully-qualified # 🚶🏻 E1.0 person walking: light skin tone +1F6B6 1F3FC ; fully-qualified # 🚶🏼 E1.0 person walking: medium-light skin tone +1F6B6 1F3FD ; fully-qualified # 🚶🏽 E1.0 person walking: medium skin tone +1F6B6 1F3FE ; fully-qualified # 🚶🏾 E1.0 person walking: medium-dark skin tone +1F6B6 1F3FF ; fully-qualified # 🚶🏿 E1.0 person walking: dark skin tone +1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ E4.0 man walking +1F6B6 200D 2642 ; minimally-qualified # 🚶‍♂ E4.0 man walking +1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ E4.0 man walking: light skin tone +1F6B6 1F3FB 200D 2642 ; minimally-qualified # 🚶🏻‍♂ E4.0 man walking: light skin tone +1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ E4.0 man walking: medium-light skin tone +1F6B6 1F3FC 200D 2642 ; minimally-qualified # 🚶🏼‍♂ E4.0 man walking: medium-light skin tone +1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ E4.0 man walking: medium skin tone +1F6B6 1F3FD 200D 2642 ; minimally-qualified # 🚶🏽‍♂ E4.0 man walking: medium skin tone +1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ E4.0 man walking: medium-dark skin tone +1F6B6 1F3FE 200D 2642 ; minimally-qualified # 🚶🏾‍♂ E4.0 man walking: medium-dark skin tone +1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ E4.0 man walking: dark skin tone +1F6B6 1F3FF 200D 2642 ; minimally-qualified # 🚶🏿‍♂ E4.0 man walking: dark skin tone +1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ E4.0 woman walking +1F6B6 200D 2640 ; minimally-qualified # 🚶‍♀ E4.0 woman walking +1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ E4.0 woman walking: light skin tone +1F6B6 1F3FB 200D 2640 ; minimally-qualified # 🚶🏻‍♀ E4.0 woman walking: light skin tone +1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ E4.0 woman walking: medium-light skin tone +1F6B6 1F3FC 200D 2640 ; minimally-qualified # 🚶🏼‍♀ E4.0 woman walking: medium-light skin tone +1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ E4.0 woman walking: medium skin tone +1F6B6 1F3FD 200D 2640 ; minimally-qualified # 🚶🏽‍♀ E4.0 woman walking: medium skin tone +1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ E4.0 woman walking: medium-dark skin tone +1F6B6 1F3FE 200D 2640 ; minimally-qualified # 🚶🏾‍♀ E4.0 woman walking: medium-dark skin tone +1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ E4.0 woman walking: dark skin tone +1F6B6 1F3FF 200D 2640 ; minimally-qualified # 🚶🏿‍♀ E4.0 woman walking: dark skin tone +1F9CD ; fully-qualified # 🧍 E12.0 person standing +1F9CD 1F3FB ; fully-qualified # 🧍🏻 E12.0 person standing: light skin tone +1F9CD 1F3FC ; fully-qualified # 🧍🏼 E12.0 person standing: medium-light skin tone +1F9CD 1F3FD ; fully-qualified # 🧍🏽 E12.0 person standing: medium skin tone +1F9CD 1F3FE ; fully-qualified # 🧍🏾 E12.0 person standing: medium-dark skin tone +1F9CD 1F3FF ; fully-qualified # 🧍🏿 E12.0 person standing: dark skin tone +1F9CD 200D 2642 FE0F ; fully-qualified # 🧍‍♂️ E12.0 man standing +1F9CD 200D 2642 ; minimally-qualified # 🧍‍♂ E12.0 man standing +1F9CD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧍🏻‍♂️ E12.0 man standing: light skin tone +1F9CD 1F3FB 200D 2642 ; minimally-qualified # 🧍🏻‍♂ E12.0 man standing: light skin tone +1F9CD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧍🏼‍♂️ E12.0 man standing: medium-light skin tone +1F9CD 1F3FC 200D 2642 ; minimally-qualified # 🧍🏼‍♂ E12.0 man standing: medium-light skin tone +1F9CD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧍🏽‍♂️ E12.0 man standing: medium skin tone +1F9CD 1F3FD 200D 2642 ; minimally-qualified # 🧍🏽‍♂ E12.0 man standing: medium skin tone +1F9CD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧍🏾‍♂️ E12.0 man standing: medium-dark skin tone +1F9CD 1F3FE 200D 2642 ; minimally-qualified # 🧍🏾‍♂ E12.0 man standing: medium-dark skin tone +1F9CD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧍🏿‍♂️ E12.0 man standing: dark skin tone +1F9CD 1F3FF 200D 2642 ; minimally-qualified # 🧍🏿‍♂ E12.0 man standing: dark skin tone +1F9CD 200D 2640 FE0F ; fully-qualified # 🧍‍♀️ E12.0 woman standing +1F9CD 200D 2640 ; minimally-qualified # 🧍‍♀ E12.0 woman standing +1F9CD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧍🏻‍♀️ E12.0 woman standing: light skin tone +1F9CD 1F3FB 200D 2640 ; minimally-qualified # 🧍🏻‍♀ E12.0 woman standing: light skin tone +1F9CD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧍🏼‍♀️ E12.0 woman standing: medium-light skin tone +1F9CD 1F3FC 200D 2640 ; minimally-qualified # 🧍🏼‍♀ E12.0 woman standing: medium-light skin tone +1F9CD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧍🏽‍♀️ E12.0 woman standing: medium skin tone +1F9CD 1F3FD 200D 2640 ; minimally-qualified # 🧍🏽‍♀ E12.0 woman standing: medium skin tone +1F9CD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧍🏾‍♀️ E12.0 woman standing: medium-dark skin tone +1F9CD 1F3FE 200D 2640 ; minimally-qualified # 🧍🏾‍♀ E12.0 woman standing: medium-dark skin tone +1F9CD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧍🏿‍♀️ E12.0 woman standing: dark skin tone +1F9CD 1F3FF 200D 2640 ; minimally-qualified # 🧍🏿‍♀ E12.0 woman standing: dark skin tone +1F9CE ; fully-qualified # 🧎 E12.0 person kneeling +1F9CE 1F3FB ; fully-qualified # 🧎🏻 E12.0 person kneeling: light skin tone +1F9CE 1F3FC ; fully-qualified # 🧎🏼 E12.0 person kneeling: medium-light skin tone +1F9CE 1F3FD ; fully-qualified # 🧎🏽 E12.0 person kneeling: medium skin tone +1F9CE 1F3FE ; fully-qualified # 🧎🏾 E12.0 person kneeling: medium-dark skin tone +1F9CE 1F3FF ; fully-qualified # 🧎🏿 E12.0 person kneeling: dark skin tone +1F9CE 200D 2642 FE0F ; fully-qualified # 🧎‍♂️ E12.0 man kneeling +1F9CE 200D 2642 ; minimally-qualified # 🧎‍♂ E12.0 man kneeling +1F9CE 1F3FB 200D 2642 FE0F ; fully-qualified # 🧎🏻‍♂️ E12.0 man kneeling: light skin tone +1F9CE 1F3FB 200D 2642 ; minimally-qualified # 🧎🏻‍♂ E12.0 man kneeling: light skin tone +1F9CE 1F3FC 200D 2642 FE0F ; fully-qualified # 🧎🏼‍♂️ E12.0 man kneeling: medium-light skin tone +1F9CE 1F3FC 200D 2642 ; minimally-qualified # 🧎🏼‍♂ E12.0 man kneeling: medium-light skin tone +1F9CE 1F3FD 200D 2642 FE0F ; fully-qualified # 🧎🏽‍♂️ E12.0 man kneeling: medium skin tone +1F9CE 1F3FD 200D 2642 ; minimally-qualified # 🧎🏽‍♂ E12.0 man kneeling: medium skin tone +1F9CE 1F3FE 200D 2642 FE0F ; fully-qualified # 🧎🏾‍♂️ E12.0 man kneeling: medium-dark skin tone +1F9CE 1F3FE 200D 2642 ; minimally-qualified # 🧎🏾‍♂ E12.0 man kneeling: medium-dark skin tone +1F9CE 1F3FF 200D 2642 FE0F ; fully-qualified # 🧎🏿‍♂️ E12.0 man kneeling: dark skin tone +1F9CE 1F3FF 200D 2642 ; minimally-qualified # 🧎🏿‍♂ E12.0 man kneeling: dark skin tone +1F9CE 200D 2640 FE0F ; fully-qualified # 🧎‍♀️ E12.0 woman kneeling +1F9CE 200D 2640 ; minimally-qualified # 🧎‍♀ E12.0 woman kneeling +1F9CE 1F3FB 200D 2640 FE0F ; fully-qualified # 🧎🏻‍♀️ E12.0 woman kneeling: light skin tone +1F9CE 1F3FB 200D 2640 ; minimally-qualified # 🧎🏻‍♀ E12.0 woman kneeling: light skin tone +1F9CE 1F3FC 200D 2640 FE0F ; fully-qualified # 🧎🏼‍♀️ E12.0 woman kneeling: medium-light skin tone +1F9CE 1F3FC 200D 2640 ; minimally-qualified # 🧎🏼‍♀ E12.0 woman kneeling: medium-light skin tone +1F9CE 1F3FD 200D 2640 FE0F ; fully-qualified # 🧎🏽‍♀️ E12.0 woman kneeling: medium skin tone +1F9CE 1F3FD 200D 2640 ; minimally-qualified # 🧎🏽‍♀ E12.0 woman kneeling: medium skin tone +1F9CE 1F3FE 200D 2640 FE0F ; fully-qualified # 🧎🏾‍♀️ E12.0 woman kneeling: medium-dark skin tone +1F9CE 1F3FE 200D 2640 ; minimally-qualified # 🧎🏾‍♀ E12.0 woman kneeling: medium-dark skin tone +1F9CE 1F3FF 200D 2640 FE0F ; fully-qualified # 🧎🏿‍♀️ E12.0 woman kneeling: dark skin tone +1F9CE 1F3FF 200D 2640 ; minimally-qualified # 🧎🏿‍♀ E12.0 woman kneeling: dark skin tone +1F9D1 200D 1F9AF ; fully-qualified # 🧑‍🦯 E12.1 person with probing cane +1F9D1 1F3FB 200D 1F9AF ; fully-qualified # 🧑🏻‍🦯 E12.1 person with probing cane: light skin tone +1F9D1 1F3FC 200D 1F9AF ; fully-qualified # 🧑🏼‍🦯 E12.1 person with probing cane: medium-light skin tone +1F9D1 1F3FD 200D 1F9AF ; fully-qualified # 🧑🏽‍🦯 E12.1 person with probing cane: medium skin tone +1F9D1 1F3FE 200D 1F9AF ; fully-qualified # 🧑🏾‍🦯 E12.1 person with probing cane: medium-dark skin tone +1F9D1 1F3FF 200D 1F9AF ; fully-qualified # 🧑🏿‍🦯 E12.1 person with probing cane: dark skin tone +1F468 200D 1F9AF ; fully-qualified # 👨‍🦯 E12.0 man with probing cane +1F468 1F3FB 200D 1F9AF ; fully-qualified # 👨🏻‍🦯 E12.0 man with probing cane: light skin tone +1F468 1F3FC 200D 1F9AF ; fully-qualified # 👨🏼‍🦯 E12.0 man with probing cane: medium-light skin tone +1F468 1F3FD 200D 1F9AF ; fully-qualified # 👨🏽‍🦯 E12.0 man with probing cane: medium skin tone +1F468 1F3FE 200D 1F9AF ; fully-qualified # 👨🏾‍🦯 E12.0 man with probing cane: medium-dark skin tone +1F468 1F3FF 200D 1F9AF ; fully-qualified # 👨🏿‍🦯 E12.0 man with probing cane: dark skin tone +1F469 200D 1F9AF ; fully-qualified # 👩‍🦯 E12.0 woman with probing cane +1F469 1F3FB 200D 1F9AF ; fully-qualified # 👩🏻‍🦯 E12.0 woman with probing cane: light skin tone +1F469 1F3FC 200D 1F9AF ; fully-qualified # 👩🏼‍🦯 E12.0 woman with probing cane: medium-light skin tone +1F469 1F3FD 200D 1F9AF ; fully-qualified # 👩🏽‍🦯 E12.0 woman with probing cane: medium skin tone +1F469 1F3FE 200D 1F9AF ; fully-qualified # 👩🏾‍🦯 E12.0 woman with probing cane: medium-dark skin tone +1F469 1F3FF 200D 1F9AF ; fully-qualified # 👩🏿‍🦯 E12.0 woman with probing cane: dark skin tone +1F9D1 200D 1F9BC ; fully-qualified # 🧑‍🦼 E12.1 person in motorized wheelchair +1F9D1 1F3FB 200D 1F9BC ; fully-qualified # 🧑🏻‍🦼 E12.1 person in motorized wheelchair: light skin tone +1F9D1 1F3FC 200D 1F9BC ; fully-qualified # 🧑🏼‍🦼 E12.1 person in motorized wheelchair: medium-light skin tone +1F9D1 1F3FD 200D 1F9BC ; fully-qualified # 🧑🏽‍🦼 E12.1 person in motorized wheelchair: medium skin tone +1F9D1 1F3FE 200D 1F9BC ; fully-qualified # 🧑🏾‍🦼 E12.1 person in motorized wheelchair: medium-dark skin tone +1F9D1 1F3FF 200D 1F9BC ; fully-qualified # 🧑🏿‍🦼 E12.1 person in motorized wheelchair: dark skin tone +1F468 200D 1F9BC ; fully-qualified # 👨‍🦼 E12.0 man in motorized wheelchair +1F468 1F3FB 200D 1F9BC ; fully-qualified # 👨🏻‍🦼 E12.0 man in motorized wheelchair: light skin tone +1F468 1F3FC 200D 1F9BC ; fully-qualified # 👨🏼‍🦼 E12.0 man in motorized wheelchair: medium-light skin tone +1F468 1F3FD 200D 1F9BC ; fully-qualified # 👨🏽‍🦼 E12.0 man in motorized wheelchair: medium skin tone +1F468 1F3FE 200D 1F9BC ; fully-qualified # 👨🏾‍🦼 E12.0 man in motorized wheelchair: medium-dark skin tone +1F468 1F3FF 200D 1F9BC ; fully-qualified # 👨🏿‍🦼 E12.0 man in motorized wheelchair: dark skin tone +1F469 200D 1F9BC ; fully-qualified # 👩‍🦼 E12.0 woman in motorized wheelchair +1F469 1F3FB 200D 1F9BC ; fully-qualified # 👩🏻‍🦼 E12.0 woman in motorized wheelchair: light skin tone +1F469 1F3FC 200D 1F9BC ; fully-qualified # 👩🏼‍🦼 E12.0 woman in motorized wheelchair: medium-light skin tone +1F469 1F3FD 200D 1F9BC ; fully-qualified # 👩🏽‍🦼 E12.0 woman in motorized wheelchair: medium skin tone +1F469 1F3FE 200D 1F9BC ; fully-qualified # 👩🏾‍🦼 E12.0 woman in motorized wheelchair: medium-dark skin tone +1F469 1F3FF 200D 1F9BC ; fully-qualified # 👩🏿‍🦼 E12.0 woman in motorized wheelchair: dark skin tone +1F9D1 200D 1F9BD ; fully-qualified # 🧑‍🦽 E12.1 person in manual wheelchair +1F9D1 1F3FB 200D 1F9BD ; fully-qualified # 🧑🏻‍🦽 E12.1 person in manual wheelchair: light skin tone +1F9D1 1F3FC 200D 1F9BD ; fully-qualified # 🧑🏼‍🦽 E12.1 person in manual wheelchair: medium-light skin tone +1F9D1 1F3FD 200D 1F9BD ; fully-qualified # 🧑🏽‍🦽 E12.1 person in manual wheelchair: medium skin tone +1F9D1 1F3FE 200D 1F9BD ; fully-qualified # 🧑🏾‍🦽 E12.1 person in manual wheelchair: medium-dark skin tone +1F9D1 1F3FF 200D 1F9BD ; fully-qualified # 🧑🏿‍🦽 E12.1 person in manual wheelchair: dark skin tone +1F468 200D 1F9BD ; fully-qualified # 👨‍🦽 E12.0 man in manual wheelchair +1F468 1F3FB 200D 1F9BD ; fully-qualified # 👨🏻‍🦽 E12.0 man in manual wheelchair: light skin tone +1F468 1F3FC 200D 1F9BD ; fully-qualified # 👨🏼‍🦽 E12.0 man in manual wheelchair: medium-light skin tone +1F468 1F3FD 200D 1F9BD ; fully-qualified # 👨🏽‍🦽 E12.0 man in manual wheelchair: medium skin tone +1F468 1F3FE 200D 1F9BD ; fully-qualified # 👨🏾‍🦽 E12.0 man in manual wheelchair: medium-dark skin tone +1F468 1F3FF 200D 1F9BD ; fully-qualified # 👨🏿‍🦽 E12.0 man in manual wheelchair: dark skin tone +1F469 200D 1F9BD ; fully-qualified # 👩‍🦽 E12.0 woman in manual wheelchair +1F469 1F3FB 200D 1F9BD ; fully-qualified # 👩🏻‍🦽 E12.0 woman in manual wheelchair: light skin tone +1F469 1F3FC 200D 1F9BD ; fully-qualified # 👩🏼‍🦽 E12.0 woman in manual wheelchair: medium-light skin tone +1F469 1F3FD 200D 1F9BD ; fully-qualified # 👩🏽‍🦽 E12.0 woman in manual wheelchair: medium skin tone +1F469 1F3FE 200D 1F9BD ; fully-qualified # 👩🏾‍🦽 E12.0 woman in manual wheelchair: medium-dark skin tone +1F469 1F3FF 200D 1F9BD ; fully-qualified # 👩🏿‍🦽 E12.0 woman in manual wheelchair: dark skin tone +1F3C3 ; fully-qualified # 🏃 E0.6 person running +1F3C3 1F3FB ; fully-qualified # 🏃🏻 E1.0 person running: light skin tone +1F3C3 1F3FC ; fully-qualified # 🏃🏼 E1.0 person running: medium-light skin tone +1F3C3 1F3FD ; fully-qualified # 🏃🏽 E1.0 person running: medium skin tone +1F3C3 1F3FE ; fully-qualified # 🏃🏾 E1.0 person running: medium-dark skin tone +1F3C3 1F3FF ; fully-qualified # 🏃🏿 E1.0 person running: dark skin tone +1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ E4.0 man running +1F3C3 200D 2642 ; minimally-qualified # 🏃‍♂ E4.0 man running +1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ E4.0 man running: light skin tone +1F3C3 1F3FB 200D 2642 ; minimally-qualified # 🏃🏻‍♂ E4.0 man running: light skin tone +1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ E4.0 man running: medium-light skin tone +1F3C3 1F3FC 200D 2642 ; minimally-qualified # 🏃🏼‍♂ E4.0 man running: medium-light skin tone +1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ E4.0 man running: medium skin tone +1F3C3 1F3FD 200D 2642 ; minimally-qualified # 🏃🏽‍♂ E4.0 man running: medium skin tone +1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ E4.0 man running: medium-dark skin tone +1F3C3 1F3FE 200D 2642 ; minimally-qualified # 🏃🏾‍♂ E4.0 man running: medium-dark skin tone +1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ E4.0 man running: dark skin tone +1F3C3 1F3FF 200D 2642 ; minimally-qualified # 🏃🏿‍♂ E4.0 man running: dark skin tone +1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ E4.0 woman running +1F3C3 200D 2640 ; minimally-qualified # 🏃‍♀ E4.0 woman running +1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ E4.0 woman running: light skin tone +1F3C3 1F3FB 200D 2640 ; minimally-qualified # 🏃🏻‍♀ E4.0 woman running: light skin tone +1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ E4.0 woman running: medium-light skin tone +1F3C3 1F3FC 200D 2640 ; minimally-qualified # 🏃🏼‍♀ E4.0 woman running: medium-light skin tone +1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ E4.0 woman running: medium skin tone +1F3C3 1F3FD 200D 2640 ; minimally-qualified # 🏃🏽‍♀ E4.0 woman running: medium skin tone +1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ E4.0 woman running: medium-dark skin tone +1F3C3 1F3FE 200D 2640 ; minimally-qualified # 🏃🏾‍♀ E4.0 woman running: medium-dark skin tone +1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ E4.0 woman running: dark skin tone +1F3C3 1F3FF 200D 2640 ; minimally-qualified # 🏃🏿‍♀ E4.0 woman running: dark skin tone +1F483 ; fully-qualified # 💃 E0.6 woman dancing +1F483 1F3FB ; fully-qualified # 💃🏻 E1.0 woman dancing: light skin tone +1F483 1F3FC ; fully-qualified # 💃🏼 E1.0 woman dancing: medium-light skin tone +1F483 1F3FD ; fully-qualified # 💃🏽 E1.0 woman dancing: medium skin tone +1F483 1F3FE ; fully-qualified # 💃🏾 E1.0 woman dancing: medium-dark skin tone +1F483 1F3FF ; fully-qualified # 💃🏿 E1.0 woman dancing: dark skin tone +1F57A ; fully-qualified # 🕺 E3.0 man dancing +1F57A 1F3FB ; fully-qualified # 🕺🏻 E3.0 man dancing: light skin tone +1F57A 1F3FC ; fully-qualified # 🕺🏼 E3.0 man dancing: medium-light skin tone +1F57A 1F3FD ; fully-qualified # 🕺🏽 E3.0 man dancing: medium skin tone +1F57A 1F3FE ; fully-qualified # 🕺🏾 E3.0 man dancing: medium-dark skin tone +1F57A 1F3FF ; fully-qualified # 🕺🏿 E3.0 man dancing: dark skin tone +1F574 FE0F ; fully-qualified # 🕴️ E0.7 man in suit levitating +1F574 ; unqualified # 🕴 E0.7 man in suit levitating +1F574 1F3FB ; fully-qualified # 🕴🏻 E4.0 man in suit levitating: light skin tone +1F574 1F3FC ; fully-qualified # 🕴🏼 E4.0 man in suit levitating: medium-light skin tone +1F574 1F3FD ; fully-qualified # 🕴🏽 E4.0 man in suit levitating: medium skin tone +1F574 1F3FE ; fully-qualified # 🕴🏾 E4.0 man in suit levitating: medium-dark skin tone +1F574 1F3FF ; fully-qualified # 🕴🏿 E4.0 man in suit levitating: dark skin tone +1F46F ; fully-qualified # 👯 E0.6 people with bunny ears +1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ E4.0 men with bunny ears +1F46F 200D 2642 ; minimally-qualified # 👯‍♂ E4.0 men with bunny ears +1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ E4.0 women with bunny ears +1F46F 200D 2640 ; minimally-qualified # 👯‍♀ E4.0 women with bunny ears +1F9D6 ; fully-qualified # 🧖 E5.0 person in steamy room +1F9D6 1F3FB ; fully-qualified # 🧖🏻 E5.0 person in steamy room: light skin tone +1F9D6 1F3FC ; fully-qualified # 🧖🏼 E5.0 person in steamy room: medium-light skin tone +1F9D6 1F3FD ; fully-qualified # 🧖🏽 E5.0 person in steamy room: medium skin tone +1F9D6 1F3FE ; fully-qualified # 🧖🏾 E5.0 person in steamy room: medium-dark skin tone +1F9D6 1F3FF ; fully-qualified # 🧖🏿 E5.0 person in steamy room: dark skin tone +1F9D6 200D 2642 FE0F ; fully-qualified # 🧖‍♂️ E5.0 man in steamy room +1F9D6 200D 2642 ; minimally-qualified # 🧖‍♂ E5.0 man in steamy room +1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻‍♂️ E5.0 man in steamy room: light skin tone +1F9D6 1F3FB 200D 2642 ; minimally-qualified # 🧖🏻‍♂ E5.0 man in steamy room: light skin tone +1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼‍♂️ E5.0 man in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2642 ; minimally-qualified # 🧖🏼‍♂ E5.0 man in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽‍♂️ E5.0 man in steamy room: medium skin tone +1F9D6 1F3FD 200D 2642 ; minimally-qualified # 🧖🏽‍♂ E5.0 man in steamy room: medium skin tone +1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾‍♂️ E5.0 man in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2642 ; minimally-qualified # 🧖🏾‍♂ E5.0 man in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿‍♂️ E5.0 man in steamy room: dark skin tone +1F9D6 1F3FF 200D 2642 ; minimally-qualified # 🧖🏿‍♂ E5.0 man in steamy room: dark skin tone +1F9D6 200D 2640 FE0F ; fully-qualified # 🧖‍♀️ E5.0 woman in steamy room +1F9D6 200D 2640 ; minimally-qualified # 🧖‍♀ E5.0 woman in steamy room +1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻‍♀️ E5.0 woman in steamy room: light skin tone +1F9D6 1F3FB 200D 2640 ; minimally-qualified # 🧖🏻‍♀ E5.0 woman in steamy room: light skin tone +1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼‍♀️ E5.0 woman in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2640 ; minimally-qualified # 🧖🏼‍♀ E5.0 woman in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽‍♀️ E5.0 woman in steamy room: medium skin tone +1F9D6 1F3FD 200D 2640 ; minimally-qualified # 🧖🏽‍♀ E5.0 woman in steamy room: medium skin tone +1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾‍♀️ E5.0 woman in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2640 ; minimally-qualified # 🧖🏾‍♀ E5.0 woman in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿‍♀️ E5.0 woman in steamy room: dark skin tone +1F9D6 1F3FF 200D 2640 ; minimally-qualified # 🧖🏿‍♀ E5.0 woman in steamy room: dark skin tone +1F9D7 ; fully-qualified # 🧗 E5.0 person climbing +1F9D7 1F3FB ; fully-qualified # 🧗🏻 E5.0 person climbing: light skin tone +1F9D7 1F3FC ; fully-qualified # 🧗🏼 E5.0 person climbing: medium-light skin tone +1F9D7 1F3FD ; fully-qualified # 🧗🏽 E5.0 person climbing: medium skin tone +1F9D7 1F3FE ; fully-qualified # 🧗🏾 E5.0 person climbing: medium-dark skin tone +1F9D7 1F3FF ; fully-qualified # 🧗🏿 E5.0 person climbing: dark skin tone +1F9D7 200D 2642 FE0F ; fully-qualified # 🧗‍♂️ E5.0 man climbing +1F9D7 200D 2642 ; minimally-qualified # 🧗‍♂ E5.0 man climbing +1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻‍♂️ E5.0 man climbing: light skin tone +1F9D7 1F3FB 200D 2642 ; minimally-qualified # 🧗🏻‍♂ E5.0 man climbing: light skin tone +1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼‍♂️ E5.0 man climbing: medium-light skin tone +1F9D7 1F3FC 200D 2642 ; minimally-qualified # 🧗🏼‍♂ E5.0 man climbing: medium-light skin tone +1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽‍♂️ E5.0 man climbing: medium skin tone +1F9D7 1F3FD 200D 2642 ; minimally-qualified # 🧗🏽‍♂ E5.0 man climbing: medium skin tone +1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾‍♂️ E5.0 man climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2642 ; minimally-qualified # 🧗🏾‍♂ E5.0 man climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿‍♂️ E5.0 man climbing: dark skin tone +1F9D7 1F3FF 200D 2642 ; minimally-qualified # 🧗🏿‍♂ E5.0 man climbing: dark skin tone +1F9D7 200D 2640 FE0F ; fully-qualified # 🧗‍♀️ E5.0 woman climbing +1F9D7 200D 2640 ; minimally-qualified # 🧗‍♀ E5.0 woman climbing +1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻‍♀️ E5.0 woman climbing: light skin tone +1F9D7 1F3FB 200D 2640 ; minimally-qualified # 🧗🏻‍♀ E5.0 woman climbing: light skin tone +1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼‍♀️ E5.0 woman climbing: medium-light skin tone +1F9D7 1F3FC 200D 2640 ; minimally-qualified # 🧗🏼‍♀ E5.0 woman climbing: medium-light skin tone +1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽‍♀️ E5.0 woman climbing: medium skin tone +1F9D7 1F3FD 200D 2640 ; minimally-qualified # 🧗🏽‍♀ E5.0 woman climbing: medium skin tone +1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾‍♀️ E5.0 woman climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2640 ; minimally-qualified # 🧗🏾‍♀ E5.0 woman climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿‍♀️ E5.0 woman climbing: dark skin tone +1F9D7 1F3FF 200D 2640 ; minimally-qualified # 🧗🏿‍♀ E5.0 woman climbing: dark skin tone +1F977 ; fully-qualified # 🥷 E13.0 ninja + +# subgroup: person-sport +1F93A ; fully-qualified # 🤺 E3.0 person fencing +1F3C7 ; fully-qualified # 🏇 E1.0 horse racing +1F3C7 1F3FB ; fully-qualified # 🏇🏻 E1.0 horse racing: light skin tone +1F3C7 1F3FC ; fully-qualified # 🏇🏼 E1.0 horse racing: medium-light skin tone +1F3C7 1F3FD ; fully-qualified # 🏇🏽 E1.0 horse racing: medium skin tone +1F3C7 1F3FE ; fully-qualified # 🏇🏾 E1.0 horse racing: medium-dark skin tone +1F3C7 1F3FF ; fully-qualified # 🏇🏿 E1.0 horse racing: dark skin tone +26F7 FE0F ; fully-qualified # ⛷️ E0.7 skier +26F7 ; unqualified # ⛷ E0.7 skier +1F3C2 ; fully-qualified # 🏂 E0.6 snowboarder +1F3C2 1F3FB ; fully-qualified # 🏂🏻 E1.0 snowboarder: light skin tone +1F3C2 1F3FC ; fully-qualified # 🏂🏼 E1.0 snowboarder: medium-light skin tone +1F3C2 1F3FD ; fully-qualified # 🏂🏽 E1.0 snowboarder: medium skin tone +1F3C2 1F3FE ; fully-qualified # 🏂🏾 E1.0 snowboarder: medium-dark skin tone +1F3C2 1F3FF ; fully-qualified # 🏂🏿 E1.0 snowboarder: dark skin tone +1F3CC FE0F ; fully-qualified # 🏌️ E0.7 person golfing +1F3CC ; unqualified # 🏌 E0.7 person golfing +1F3CC 1F3FB ; fully-qualified # 🏌🏻 E4.0 person golfing: light skin tone +1F3CC 1F3FC ; fully-qualified # 🏌🏼 E4.0 person golfing: medium-light skin tone +1F3CC 1F3FD ; fully-qualified # 🏌🏽 E4.0 person golfing: medium skin tone +1F3CC 1F3FE ; fully-qualified # 🏌🏾 E4.0 person golfing: medium-dark skin tone +1F3CC 1F3FF ; fully-qualified # 🏌🏿 E4.0 person golfing: dark skin tone +1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ E4.0 man golfing +1F3CC 200D 2642 FE0F ; unqualified # 🏌‍♂️ E4.0 man golfing +1F3CC FE0F 200D 2642 ; unqualified # 🏌️‍♂ E4.0 man golfing +1F3CC 200D 2642 ; unqualified # 🏌‍♂ E4.0 man golfing +1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ E4.0 man golfing: light skin tone +1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻‍♂ E4.0 man golfing: light skin tone +1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ E4.0 man golfing: medium-light skin tone +1F3CC 1F3FC 200D 2642 ; minimally-qualified # 🏌🏼‍♂ E4.0 man golfing: medium-light skin tone +1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ E4.0 man golfing: medium skin tone +1F3CC 1F3FD 200D 2642 ; minimally-qualified # 🏌🏽‍♂ E4.0 man golfing: medium skin tone +1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ E4.0 man golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2642 ; minimally-qualified # 🏌🏾‍♂ E4.0 man golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ E4.0 man golfing: dark skin tone +1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿‍♂ E4.0 man golfing: dark skin tone +1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ E4.0 woman golfing +1F3CC 200D 2640 FE0F ; unqualified # 🏌‍♀️ E4.0 woman golfing +1F3CC FE0F 200D 2640 ; unqualified # 🏌️‍♀ E4.0 woman golfing +1F3CC 200D 2640 ; unqualified # 🏌‍♀ E4.0 woman golfing +1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ E4.0 woman golfing: light skin tone +1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻‍♀ E4.0 woman golfing: light skin tone +1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ E4.0 woman golfing: medium-light skin tone +1F3CC 1F3FC 200D 2640 ; minimally-qualified # 🏌🏼‍♀ E4.0 woman golfing: medium-light skin tone +1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ E4.0 woman golfing: medium skin tone +1F3CC 1F3FD 200D 2640 ; minimally-qualified # 🏌🏽‍♀ E4.0 woman golfing: medium skin tone +1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ E4.0 woman golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2640 ; minimally-qualified # 🏌🏾‍♀ E4.0 woman golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ E4.0 woman golfing: dark skin tone +1F3CC 1F3FF 200D 2640 ; minimally-qualified # 🏌🏿‍♀ E4.0 woman golfing: dark skin tone +1F3C4 ; fully-qualified # 🏄 E0.6 person surfing +1F3C4 1F3FB ; fully-qualified # 🏄🏻 E1.0 person surfing: light skin tone +1F3C4 1F3FC ; fully-qualified # 🏄🏼 E1.0 person surfing: medium-light skin tone +1F3C4 1F3FD ; fully-qualified # 🏄🏽 E1.0 person surfing: medium skin tone +1F3C4 1F3FE ; fully-qualified # 🏄🏾 E1.0 person surfing: medium-dark skin tone +1F3C4 1F3FF ; fully-qualified # 🏄🏿 E1.0 person surfing: dark skin tone +1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ E4.0 man surfing +1F3C4 200D 2642 ; minimally-qualified # 🏄‍♂ E4.0 man surfing +1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ E4.0 man surfing: light skin tone +1F3C4 1F3FB 200D 2642 ; minimally-qualified # 🏄🏻‍♂ E4.0 man surfing: light skin tone +1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ E4.0 man surfing: medium-light skin tone +1F3C4 1F3FC 200D 2642 ; minimally-qualified # 🏄🏼‍♂ E4.0 man surfing: medium-light skin tone +1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ E4.0 man surfing: medium skin tone +1F3C4 1F3FD 200D 2642 ; minimally-qualified # 🏄🏽‍♂ E4.0 man surfing: medium skin tone +1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ E4.0 man surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2642 ; minimally-qualified # 🏄🏾‍♂ E4.0 man surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ E4.0 man surfing: dark skin tone +1F3C4 1F3FF 200D 2642 ; minimally-qualified # 🏄🏿‍♂ E4.0 man surfing: dark skin tone +1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ E4.0 woman surfing +1F3C4 200D 2640 ; minimally-qualified # 🏄‍♀ E4.0 woman surfing +1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ E4.0 woman surfing: light skin tone +1F3C4 1F3FB 200D 2640 ; minimally-qualified # 🏄🏻‍♀ E4.0 woman surfing: light skin tone +1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ E4.0 woman surfing: medium-light skin tone +1F3C4 1F3FC 200D 2640 ; minimally-qualified # 🏄🏼‍♀ E4.0 woman surfing: medium-light skin tone +1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ E4.0 woman surfing: medium skin tone +1F3C4 1F3FD 200D 2640 ; minimally-qualified # 🏄🏽‍♀ E4.0 woman surfing: medium skin tone +1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ E4.0 woman surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2640 ; minimally-qualified # 🏄🏾‍♀ E4.0 woman surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ E4.0 woman surfing: dark skin tone +1F3C4 1F3FF 200D 2640 ; minimally-qualified # 🏄🏿‍♀ E4.0 woman surfing: dark skin tone +1F6A3 ; fully-qualified # 🚣 E1.0 person rowing boat +1F6A3 1F3FB ; fully-qualified # 🚣🏻 E1.0 person rowing boat: light skin tone +1F6A3 1F3FC ; fully-qualified # 🚣🏼 E1.0 person rowing boat: medium-light skin tone +1F6A3 1F3FD ; fully-qualified # 🚣🏽 E1.0 person rowing boat: medium skin tone +1F6A3 1F3FE ; fully-qualified # 🚣🏾 E1.0 person rowing boat: medium-dark skin tone +1F6A3 1F3FF ; fully-qualified # 🚣🏿 E1.0 person rowing boat: dark skin tone +1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ E4.0 man rowing boat +1F6A3 200D 2642 ; minimally-qualified # 🚣‍♂ E4.0 man rowing boat +1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ E4.0 man rowing boat: light skin tone +1F6A3 1F3FB 200D 2642 ; minimally-qualified # 🚣🏻‍♂ E4.0 man rowing boat: light skin tone +1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ E4.0 man rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2642 ; minimally-qualified # 🚣🏼‍♂ E4.0 man rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ E4.0 man rowing boat: medium skin tone +1F6A3 1F3FD 200D 2642 ; minimally-qualified # 🚣🏽‍♂ E4.0 man rowing boat: medium skin tone +1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ E4.0 man rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2642 ; minimally-qualified # 🚣🏾‍♂ E4.0 man rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ E4.0 man rowing boat: dark skin tone +1F6A3 1F3FF 200D 2642 ; minimally-qualified # 🚣🏿‍♂ E4.0 man rowing boat: dark skin tone +1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ E4.0 woman rowing boat +1F6A3 200D 2640 ; minimally-qualified # 🚣‍♀ E4.0 woman rowing boat +1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ E4.0 woman rowing boat: light skin tone +1F6A3 1F3FB 200D 2640 ; minimally-qualified # 🚣🏻‍♀ E4.0 woman rowing boat: light skin tone +1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ E4.0 woman rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2640 ; minimally-qualified # 🚣🏼‍♀ E4.0 woman rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ E4.0 woman rowing boat: medium skin tone +1F6A3 1F3FD 200D 2640 ; minimally-qualified # 🚣🏽‍♀ E4.0 woman rowing boat: medium skin tone +1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ E4.0 woman rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2640 ; minimally-qualified # 🚣🏾‍♀ E4.0 woman rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ E4.0 woman rowing boat: dark skin tone +1F6A3 1F3FF 200D 2640 ; minimally-qualified # 🚣🏿‍♀ E4.0 woman rowing boat: dark skin tone +1F3CA ; fully-qualified # 🏊 E0.6 person swimming +1F3CA 1F3FB ; fully-qualified # 🏊🏻 E1.0 person swimming: light skin tone +1F3CA 1F3FC ; fully-qualified # 🏊🏼 E1.0 person swimming: medium-light skin tone +1F3CA 1F3FD ; fully-qualified # 🏊🏽 E1.0 person swimming: medium skin tone +1F3CA 1F3FE ; fully-qualified # 🏊🏾 E1.0 person swimming: medium-dark skin tone +1F3CA 1F3FF ; fully-qualified # 🏊🏿 E1.0 person swimming: dark skin tone +1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ E4.0 man swimming +1F3CA 200D 2642 ; minimally-qualified # 🏊‍♂ E4.0 man swimming +1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ E4.0 man swimming: light skin tone +1F3CA 1F3FB 200D 2642 ; minimally-qualified # 🏊🏻‍♂ E4.0 man swimming: light skin tone +1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ E4.0 man swimming: medium-light skin tone +1F3CA 1F3FC 200D 2642 ; minimally-qualified # 🏊🏼‍♂ E4.0 man swimming: medium-light skin tone +1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ E4.0 man swimming: medium skin tone +1F3CA 1F3FD 200D 2642 ; minimally-qualified # 🏊🏽‍♂ E4.0 man swimming: medium skin tone +1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ E4.0 man swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2642 ; minimally-qualified # 🏊🏾‍♂ E4.0 man swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ E4.0 man swimming: dark skin tone +1F3CA 1F3FF 200D 2642 ; minimally-qualified # 🏊🏿‍♂ E4.0 man swimming: dark skin tone +1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ E4.0 woman swimming +1F3CA 200D 2640 ; minimally-qualified # 🏊‍♀ E4.0 woman swimming +1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ E4.0 woman swimming: light skin tone +1F3CA 1F3FB 200D 2640 ; minimally-qualified # 🏊🏻‍♀ E4.0 woman swimming: light skin tone +1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ E4.0 woman swimming: medium-light skin tone +1F3CA 1F3FC 200D 2640 ; minimally-qualified # 🏊🏼‍♀ E4.0 woman swimming: medium-light skin tone +1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ E4.0 woman swimming: medium skin tone +1F3CA 1F3FD 200D 2640 ; minimally-qualified # 🏊🏽‍♀ E4.0 woman swimming: medium skin tone +1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ E4.0 woman swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2640 ; minimally-qualified # 🏊🏾‍♀ E4.0 woman swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ E4.0 woman swimming: dark skin tone +1F3CA 1F3FF 200D 2640 ; minimally-qualified # 🏊🏿‍♀ E4.0 woman swimming: dark skin tone +26F9 FE0F ; fully-qualified # ⛹️ E0.7 person bouncing ball +26F9 ; unqualified # ⛹ E0.7 person bouncing ball +26F9 1F3FB ; fully-qualified # ⛹🏻 E2.0 person bouncing ball: light skin tone +26F9 1F3FC ; fully-qualified # ⛹🏼 E2.0 person bouncing ball: medium-light skin tone +26F9 1F3FD ; fully-qualified # ⛹🏽 E2.0 person bouncing ball: medium skin tone +26F9 1F3FE ; fully-qualified # ⛹🏾 E2.0 person bouncing ball: medium-dark skin tone +26F9 1F3FF ; fully-qualified # ⛹🏿 E2.0 person bouncing ball: dark skin tone +26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ E4.0 man bouncing ball +26F9 200D 2642 FE0F ; unqualified # ⛹‍♂️ E4.0 man bouncing ball +26F9 FE0F 200D 2642 ; unqualified # ⛹️‍♂ E4.0 man bouncing ball +26F9 200D 2642 ; unqualified # ⛹‍♂ E4.0 man bouncing ball +26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ E4.0 man bouncing ball: light skin tone +26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻‍♂ E4.0 man bouncing ball: light skin tone +26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ E4.0 man bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2642 ; minimally-qualified # ⛹🏼‍♂ E4.0 man bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ E4.0 man bouncing ball: medium skin tone +26F9 1F3FD 200D 2642 ; minimally-qualified # ⛹🏽‍♂ E4.0 man bouncing ball: medium skin tone +26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ E4.0 man bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2642 ; minimally-qualified # ⛹🏾‍♂ E4.0 man bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ E4.0 man bouncing ball: dark skin tone +26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿‍♂ E4.0 man bouncing ball: dark skin tone +26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ E4.0 woman bouncing ball +26F9 200D 2640 FE0F ; unqualified # ⛹‍♀️ E4.0 woman bouncing ball +26F9 FE0F 200D 2640 ; unqualified # ⛹️‍♀ E4.0 woman bouncing ball +26F9 200D 2640 ; unqualified # ⛹‍♀ E4.0 woman bouncing ball +26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ E4.0 woman bouncing ball: light skin tone +26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻‍♀ E4.0 woman bouncing ball: light skin tone +26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ E4.0 woman bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2640 ; minimally-qualified # ⛹🏼‍♀ E4.0 woman bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ E4.0 woman bouncing ball: medium skin tone +26F9 1F3FD 200D 2640 ; minimally-qualified # ⛹🏽‍♀ E4.0 woman bouncing ball: medium skin tone +26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ E4.0 woman bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2640 ; minimally-qualified # ⛹🏾‍♀ E4.0 woman bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ E4.0 woman bouncing ball: dark skin tone +26F9 1F3FF 200D 2640 ; minimally-qualified # ⛹🏿‍♀ E4.0 woman bouncing ball: dark skin tone +1F3CB FE0F ; fully-qualified # 🏋️ E0.7 person lifting weights +1F3CB ; unqualified # 🏋 E0.7 person lifting weights +1F3CB 1F3FB ; fully-qualified # 🏋🏻 E2.0 person lifting weights: light skin tone +1F3CB 1F3FC ; fully-qualified # 🏋🏼 E2.0 person lifting weights: medium-light skin tone +1F3CB 1F3FD ; fully-qualified # 🏋🏽 E2.0 person lifting weights: medium skin tone +1F3CB 1F3FE ; fully-qualified # 🏋🏾 E2.0 person lifting weights: medium-dark skin tone +1F3CB 1F3FF ; fully-qualified # 🏋🏿 E2.0 person lifting weights: dark skin tone +1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ E4.0 man lifting weights +1F3CB 200D 2642 FE0F ; unqualified # 🏋‍♂️ E4.0 man lifting weights +1F3CB FE0F 200D 2642 ; unqualified # 🏋️‍♂ E4.0 man lifting weights +1F3CB 200D 2642 ; unqualified # 🏋‍♂ E4.0 man lifting weights +1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ E4.0 man lifting weights: light skin tone +1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻‍♂ E4.0 man lifting weights: light skin tone +1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ E4.0 man lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2642 ; minimally-qualified # 🏋🏼‍♂ E4.0 man lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ E4.0 man lifting weights: medium skin tone +1F3CB 1F3FD 200D 2642 ; minimally-qualified # 🏋🏽‍♂ E4.0 man lifting weights: medium skin tone +1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ E4.0 man lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2642 ; minimally-qualified # 🏋🏾‍♂ E4.0 man lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ E4.0 man lifting weights: dark skin tone +1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿‍♂ E4.0 man lifting weights: dark skin tone +1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ E4.0 woman lifting weights +1F3CB 200D 2640 FE0F ; unqualified # 🏋‍♀️ E4.0 woman lifting weights +1F3CB FE0F 200D 2640 ; unqualified # 🏋️‍♀ E4.0 woman lifting weights +1F3CB 200D 2640 ; unqualified # 🏋‍♀ E4.0 woman lifting weights +1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ E4.0 woman lifting weights: light skin tone +1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻‍♀ E4.0 woman lifting weights: light skin tone +1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ E4.0 woman lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2640 ; minimally-qualified # 🏋🏼‍♀ E4.0 woman lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ E4.0 woman lifting weights: medium skin tone +1F3CB 1F3FD 200D 2640 ; minimally-qualified # 🏋🏽‍♀ E4.0 woman lifting weights: medium skin tone +1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ E4.0 woman lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2640 ; minimally-qualified # 🏋🏾‍♀ E4.0 woman lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ E4.0 woman lifting weights: dark skin tone +1F3CB 1F3FF 200D 2640 ; minimally-qualified # 🏋🏿‍♀ E4.0 woman lifting weights: dark skin tone +1F6B4 ; fully-qualified # 🚴 E1.0 person biking +1F6B4 1F3FB ; fully-qualified # 🚴🏻 E1.0 person biking: light skin tone +1F6B4 1F3FC ; fully-qualified # 🚴🏼 E1.0 person biking: medium-light skin tone +1F6B4 1F3FD ; fully-qualified # 🚴🏽 E1.0 person biking: medium skin tone +1F6B4 1F3FE ; fully-qualified # 🚴🏾 E1.0 person biking: medium-dark skin tone +1F6B4 1F3FF ; fully-qualified # 🚴🏿 E1.0 person biking: dark skin tone +1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ E4.0 man biking +1F6B4 200D 2642 ; minimally-qualified # 🚴‍♂ E4.0 man biking +1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ E4.0 man biking: light skin tone +1F6B4 1F3FB 200D 2642 ; minimally-qualified # 🚴🏻‍♂ E4.0 man biking: light skin tone +1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ E4.0 man biking: medium-light skin tone +1F6B4 1F3FC 200D 2642 ; minimally-qualified # 🚴🏼‍♂ E4.0 man biking: medium-light skin tone +1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ E4.0 man biking: medium skin tone +1F6B4 1F3FD 200D 2642 ; minimally-qualified # 🚴🏽‍♂ E4.0 man biking: medium skin tone +1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ E4.0 man biking: medium-dark skin tone +1F6B4 1F3FE 200D 2642 ; minimally-qualified # 🚴🏾‍♂ E4.0 man biking: medium-dark skin tone +1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ E4.0 man biking: dark skin tone +1F6B4 1F3FF 200D 2642 ; minimally-qualified # 🚴🏿‍♂ E4.0 man biking: dark skin tone +1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ E4.0 woman biking +1F6B4 200D 2640 ; minimally-qualified # 🚴‍♀ E4.0 woman biking +1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ E4.0 woman biking: light skin tone +1F6B4 1F3FB 200D 2640 ; minimally-qualified # 🚴🏻‍♀ E4.0 woman biking: light skin tone +1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ E4.0 woman biking: medium-light skin tone +1F6B4 1F3FC 200D 2640 ; minimally-qualified # 🚴🏼‍♀ E4.0 woman biking: medium-light skin tone +1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ E4.0 woman biking: medium skin tone +1F6B4 1F3FD 200D 2640 ; minimally-qualified # 🚴🏽‍♀ E4.0 woman biking: medium skin tone +1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ E4.0 woman biking: medium-dark skin tone +1F6B4 1F3FE 200D 2640 ; minimally-qualified # 🚴🏾‍♀ E4.0 woman biking: medium-dark skin tone +1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ E4.0 woman biking: dark skin tone +1F6B4 1F3FF 200D 2640 ; minimally-qualified # 🚴🏿‍♀ E4.0 woman biking: dark skin tone +1F6B5 ; fully-qualified # 🚵 E1.0 person mountain biking +1F6B5 1F3FB ; fully-qualified # 🚵🏻 E1.0 person mountain biking: light skin tone +1F6B5 1F3FC ; fully-qualified # 🚵🏼 E1.0 person mountain biking: medium-light skin tone +1F6B5 1F3FD ; fully-qualified # 🚵🏽 E1.0 person mountain biking: medium skin tone +1F6B5 1F3FE ; fully-qualified # 🚵🏾 E1.0 person mountain biking: medium-dark skin tone +1F6B5 1F3FF ; fully-qualified # 🚵🏿 E1.0 person mountain biking: dark skin tone +1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ E4.0 man mountain biking +1F6B5 200D 2642 ; minimally-qualified # 🚵‍♂ E4.0 man mountain biking +1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ E4.0 man mountain biking: light skin tone +1F6B5 1F3FB 200D 2642 ; minimally-qualified # 🚵🏻‍♂ E4.0 man mountain biking: light skin tone +1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ E4.0 man mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2642 ; minimally-qualified # 🚵🏼‍♂ E4.0 man mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ E4.0 man mountain biking: medium skin tone +1F6B5 1F3FD 200D 2642 ; minimally-qualified # 🚵🏽‍♂ E4.0 man mountain biking: medium skin tone +1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ E4.0 man mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2642 ; minimally-qualified # 🚵🏾‍♂ E4.0 man mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ E4.0 man mountain biking: dark skin tone +1F6B5 1F3FF 200D 2642 ; minimally-qualified # 🚵🏿‍♂ E4.0 man mountain biking: dark skin tone +1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ E4.0 woman mountain biking +1F6B5 200D 2640 ; minimally-qualified # 🚵‍♀ E4.0 woman mountain biking +1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ E4.0 woman mountain biking: light skin tone +1F6B5 1F3FB 200D 2640 ; minimally-qualified # 🚵🏻‍♀ E4.0 woman mountain biking: light skin tone +1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ E4.0 woman mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2640 ; minimally-qualified # 🚵🏼‍♀ E4.0 woman mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ E4.0 woman mountain biking: medium skin tone +1F6B5 1F3FD 200D 2640 ; minimally-qualified # 🚵🏽‍♀ E4.0 woman mountain biking: medium skin tone +1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ E4.0 woman mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2640 ; minimally-qualified # 🚵🏾‍♀ E4.0 woman mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ E4.0 woman mountain biking: dark skin tone +1F6B5 1F3FF 200D 2640 ; minimally-qualified # 🚵🏿‍♀ E4.0 woman mountain biking: dark skin tone +1F938 ; fully-qualified # 🤸 E3.0 person cartwheeling +1F938 1F3FB ; fully-qualified # 🤸🏻 E3.0 person cartwheeling: light skin tone +1F938 1F3FC ; fully-qualified # 🤸🏼 E3.0 person cartwheeling: medium-light skin tone +1F938 1F3FD ; fully-qualified # 🤸🏽 E3.0 person cartwheeling: medium skin tone +1F938 1F3FE ; fully-qualified # 🤸🏾 E3.0 person cartwheeling: medium-dark skin tone +1F938 1F3FF ; fully-qualified # 🤸🏿 E3.0 person cartwheeling: dark skin tone +1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ E4.0 man cartwheeling +1F938 200D 2642 ; minimally-qualified # 🤸‍♂ E4.0 man cartwheeling +1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ E4.0 man cartwheeling: light skin tone +1F938 1F3FB 200D 2642 ; minimally-qualified # 🤸🏻‍♂ E4.0 man cartwheeling: light skin tone +1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ E4.0 man cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2642 ; minimally-qualified # 🤸🏼‍♂ E4.0 man cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ E4.0 man cartwheeling: medium skin tone +1F938 1F3FD 200D 2642 ; minimally-qualified # 🤸🏽‍♂ E4.0 man cartwheeling: medium skin tone +1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ E4.0 man cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2642 ; minimally-qualified # 🤸🏾‍♂ E4.0 man cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ E4.0 man cartwheeling: dark skin tone +1F938 1F3FF 200D 2642 ; minimally-qualified # 🤸🏿‍♂ E4.0 man cartwheeling: dark skin tone +1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ E4.0 woman cartwheeling +1F938 200D 2640 ; minimally-qualified # 🤸‍♀ E4.0 woman cartwheeling +1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ E4.0 woman cartwheeling: light skin tone +1F938 1F3FB 200D 2640 ; minimally-qualified # 🤸🏻‍♀ E4.0 woman cartwheeling: light skin tone +1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ E4.0 woman cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2640 ; minimally-qualified # 🤸🏼‍♀ E4.0 woman cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ E4.0 woman cartwheeling: medium skin tone +1F938 1F3FD 200D 2640 ; minimally-qualified # 🤸🏽‍♀ E4.0 woman cartwheeling: medium skin tone +1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ E4.0 woman cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2640 ; minimally-qualified # 🤸🏾‍♀ E4.0 woman cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ E4.0 woman cartwheeling: dark skin tone +1F938 1F3FF 200D 2640 ; minimally-qualified # 🤸🏿‍♀ E4.0 woman cartwheeling: dark skin tone +1F93C ; fully-qualified # 🤼 E3.0 people wrestling +1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ E4.0 men wrestling +1F93C 200D 2642 ; minimally-qualified # 🤼‍♂ E4.0 men wrestling +1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ E4.0 women wrestling +1F93C 200D 2640 ; minimally-qualified # 🤼‍♀ E4.0 women wrestling +1F93D ; fully-qualified # 🤽 E3.0 person playing water polo +1F93D 1F3FB ; fully-qualified # 🤽🏻 E3.0 person playing water polo: light skin tone +1F93D 1F3FC ; fully-qualified # 🤽🏼 E3.0 person playing water polo: medium-light skin tone +1F93D 1F3FD ; fully-qualified # 🤽🏽 E3.0 person playing water polo: medium skin tone +1F93D 1F3FE ; fully-qualified # 🤽🏾 E3.0 person playing water polo: medium-dark skin tone +1F93D 1F3FF ; fully-qualified # 🤽🏿 E3.0 person playing water polo: dark skin tone +1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ E4.0 man playing water polo +1F93D 200D 2642 ; minimally-qualified # 🤽‍♂ E4.0 man playing water polo +1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ E4.0 man playing water polo: light skin tone +1F93D 1F3FB 200D 2642 ; minimally-qualified # 🤽🏻‍♂ E4.0 man playing water polo: light skin tone +1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ E4.0 man playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2642 ; minimally-qualified # 🤽🏼‍♂ E4.0 man playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ E4.0 man playing water polo: medium skin tone +1F93D 1F3FD 200D 2642 ; minimally-qualified # 🤽🏽‍♂ E4.0 man playing water polo: medium skin tone +1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ E4.0 man playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2642 ; minimally-qualified # 🤽🏾‍♂ E4.0 man playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ E4.0 man playing water polo: dark skin tone +1F93D 1F3FF 200D 2642 ; minimally-qualified # 🤽🏿‍♂ E4.0 man playing water polo: dark skin tone +1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ E4.0 woman playing water polo +1F93D 200D 2640 ; minimally-qualified # 🤽‍♀ E4.0 woman playing water polo +1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ E4.0 woman playing water polo: light skin tone +1F93D 1F3FB 200D 2640 ; minimally-qualified # 🤽🏻‍♀ E4.0 woman playing water polo: light skin tone +1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ E4.0 woman playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2640 ; minimally-qualified # 🤽🏼‍♀ E4.0 woman playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ E4.0 woman playing water polo: medium skin tone +1F93D 1F3FD 200D 2640 ; minimally-qualified # 🤽🏽‍♀ E4.0 woman playing water polo: medium skin tone +1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ E4.0 woman playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2640 ; minimally-qualified # 🤽🏾‍♀ E4.0 woman playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ E4.0 woman playing water polo: dark skin tone +1F93D 1F3FF 200D 2640 ; minimally-qualified # 🤽🏿‍♀ E4.0 woman playing water polo: dark skin tone +1F93E ; fully-qualified # 🤾 E3.0 person playing handball +1F93E 1F3FB ; fully-qualified # 🤾🏻 E3.0 person playing handball: light skin tone +1F93E 1F3FC ; fully-qualified # 🤾🏼 E3.0 person playing handball: medium-light skin tone +1F93E 1F3FD ; fully-qualified # 🤾🏽 E3.0 person playing handball: medium skin tone +1F93E 1F3FE ; fully-qualified # 🤾🏾 E3.0 person playing handball: medium-dark skin tone +1F93E 1F3FF ; fully-qualified # 🤾🏿 E3.0 person playing handball: dark skin tone +1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ E4.0 man playing handball +1F93E 200D 2642 ; minimally-qualified # 🤾‍♂ E4.0 man playing handball +1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ E4.0 man playing handball: light skin tone +1F93E 1F3FB 200D 2642 ; minimally-qualified # 🤾🏻‍♂ E4.0 man playing handball: light skin tone +1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ E4.0 man playing handball: medium-light skin tone +1F93E 1F3FC 200D 2642 ; minimally-qualified # 🤾🏼‍♂ E4.0 man playing handball: medium-light skin tone +1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ E4.0 man playing handball: medium skin tone +1F93E 1F3FD 200D 2642 ; minimally-qualified # 🤾🏽‍♂ E4.0 man playing handball: medium skin tone +1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ E4.0 man playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2642 ; minimally-qualified # 🤾🏾‍♂ E4.0 man playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ E4.0 man playing handball: dark skin tone +1F93E 1F3FF 200D 2642 ; minimally-qualified # 🤾🏿‍♂ E4.0 man playing handball: dark skin tone +1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ E4.0 woman playing handball +1F93E 200D 2640 ; minimally-qualified # 🤾‍♀ E4.0 woman playing handball +1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ E4.0 woman playing handball: light skin tone +1F93E 1F3FB 200D 2640 ; minimally-qualified # 🤾🏻‍♀ E4.0 woman playing handball: light skin tone +1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ E4.0 woman playing handball: medium-light skin tone +1F93E 1F3FC 200D 2640 ; minimally-qualified # 🤾🏼‍♀ E4.0 woman playing handball: medium-light skin tone +1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ E4.0 woman playing handball: medium skin tone +1F93E 1F3FD 200D 2640 ; minimally-qualified # 🤾🏽‍♀ E4.0 woman playing handball: medium skin tone +1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ E4.0 woman playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2640 ; minimally-qualified # 🤾🏾‍♀ E4.0 woman playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ E4.0 woman playing handball: dark skin tone +1F93E 1F3FF 200D 2640 ; minimally-qualified # 🤾🏿‍♀ E4.0 woman playing handball: dark skin tone +1F939 ; fully-qualified # 🤹 E3.0 person juggling +1F939 1F3FB ; fully-qualified # 🤹🏻 E3.0 person juggling: light skin tone +1F939 1F3FC ; fully-qualified # 🤹🏼 E3.0 person juggling: medium-light skin tone +1F939 1F3FD ; fully-qualified # 🤹🏽 E3.0 person juggling: medium skin tone +1F939 1F3FE ; fully-qualified # 🤹🏾 E3.0 person juggling: medium-dark skin tone +1F939 1F3FF ; fully-qualified # 🤹🏿 E3.0 person juggling: dark skin tone +1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ E4.0 man juggling +1F939 200D 2642 ; minimally-qualified # 🤹‍♂ E4.0 man juggling +1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ E4.0 man juggling: light skin tone +1F939 1F3FB 200D 2642 ; minimally-qualified # 🤹🏻‍♂ E4.0 man juggling: light skin tone +1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ E4.0 man juggling: medium-light skin tone +1F939 1F3FC 200D 2642 ; minimally-qualified # 🤹🏼‍♂ E4.0 man juggling: medium-light skin tone +1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ E4.0 man juggling: medium skin tone +1F939 1F3FD 200D 2642 ; minimally-qualified # 🤹🏽‍♂ E4.0 man juggling: medium skin tone +1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ E4.0 man juggling: medium-dark skin tone +1F939 1F3FE 200D 2642 ; minimally-qualified # 🤹🏾‍♂ E4.0 man juggling: medium-dark skin tone +1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ E4.0 man juggling: dark skin tone +1F939 1F3FF 200D 2642 ; minimally-qualified # 🤹🏿‍♂ E4.0 man juggling: dark skin tone +1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ E4.0 woman juggling +1F939 200D 2640 ; minimally-qualified # 🤹‍♀ E4.0 woman juggling +1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ E4.0 woman juggling: light skin tone +1F939 1F3FB 200D 2640 ; minimally-qualified # 🤹🏻‍♀ E4.0 woman juggling: light skin tone +1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ E4.0 woman juggling: medium-light skin tone +1F939 1F3FC 200D 2640 ; minimally-qualified # 🤹🏼‍♀ E4.0 woman juggling: medium-light skin tone +1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ E4.0 woman juggling: medium skin tone +1F939 1F3FD 200D 2640 ; minimally-qualified # 🤹🏽‍♀ E4.0 woman juggling: medium skin tone +1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ E4.0 woman juggling: medium-dark skin tone +1F939 1F3FE 200D 2640 ; minimally-qualified # 🤹🏾‍♀ E4.0 woman juggling: medium-dark skin tone +1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ E4.0 woman juggling: dark skin tone +1F939 1F3FF 200D 2640 ; minimally-qualified # 🤹🏿‍♀ E4.0 woman juggling: dark skin tone + +# subgroup: person-resting +1F9D8 ; fully-qualified # 🧘 E5.0 person in lotus position +1F9D8 1F3FB ; fully-qualified # 🧘🏻 E5.0 person in lotus position: light skin tone +1F9D8 1F3FC ; fully-qualified # 🧘🏼 E5.0 person in lotus position: medium-light skin tone +1F9D8 1F3FD ; fully-qualified # 🧘🏽 E5.0 person in lotus position: medium skin tone +1F9D8 1F3FE ; fully-qualified # 🧘🏾 E5.0 person in lotus position: medium-dark skin tone +1F9D8 1F3FF ; fully-qualified # 🧘🏿 E5.0 person in lotus position: dark skin tone +1F9D8 200D 2642 FE0F ; fully-qualified # 🧘‍♂️ E5.0 man in lotus position +1F9D8 200D 2642 ; minimally-qualified # 🧘‍♂ E5.0 man in lotus position +1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻‍♂️ E5.0 man in lotus position: light skin tone +1F9D8 1F3FB 200D 2642 ; minimally-qualified # 🧘🏻‍♂ E5.0 man in lotus position: light skin tone +1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼‍♂️ E5.0 man in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2642 ; minimally-qualified # 🧘🏼‍♂ E5.0 man in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽‍♂️ E5.0 man in lotus position: medium skin tone +1F9D8 1F3FD 200D 2642 ; minimally-qualified # 🧘🏽‍♂ E5.0 man in lotus position: medium skin tone +1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾‍♂️ E5.0 man in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2642 ; minimally-qualified # 🧘🏾‍♂ E5.0 man in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿‍♂️ E5.0 man in lotus position: dark skin tone +1F9D8 1F3FF 200D 2642 ; minimally-qualified # 🧘🏿‍♂ E5.0 man in lotus position: dark skin tone +1F9D8 200D 2640 FE0F ; fully-qualified # 🧘‍♀️ E5.0 woman in lotus position +1F9D8 200D 2640 ; minimally-qualified # 🧘‍♀ E5.0 woman in lotus position +1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻‍♀️ E5.0 woman in lotus position: light skin tone +1F9D8 1F3FB 200D 2640 ; minimally-qualified # 🧘🏻‍♀ E5.0 woman in lotus position: light skin tone +1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼‍♀️ E5.0 woman in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2640 ; minimally-qualified # 🧘🏼‍♀ E5.0 woman in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽‍♀️ E5.0 woman in lotus position: medium skin tone +1F9D8 1F3FD 200D 2640 ; minimally-qualified # 🧘🏽‍♀ E5.0 woman in lotus position: medium skin tone +1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾‍♀️ E5.0 woman in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2640 ; minimally-qualified # 🧘🏾‍♀ E5.0 woman in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿‍♀️ E5.0 woman in lotus position: dark skin tone +1F9D8 1F3FF 200D 2640 ; minimally-qualified # 🧘🏿‍♀ E5.0 woman in lotus position: dark skin tone +1F6C0 ; fully-qualified # 🛀 E0.6 person taking bath +1F6C0 1F3FB ; fully-qualified # 🛀🏻 E1.0 person taking bath: light skin tone +1F6C0 1F3FC ; fully-qualified # 🛀🏼 E1.0 person taking bath: medium-light skin tone +1F6C0 1F3FD ; fully-qualified # 🛀🏽 E1.0 person taking bath: medium skin tone +1F6C0 1F3FE ; fully-qualified # 🛀🏾 E1.0 person taking bath: medium-dark skin tone +1F6C0 1F3FF ; fully-qualified # 🛀🏿 E1.0 person taking bath: dark skin tone +1F6CC ; fully-qualified # 🛌 E1.0 person in bed +1F6CC 1F3FB ; fully-qualified # 🛌🏻 E4.0 person in bed: light skin tone +1F6CC 1F3FC ; fully-qualified # 🛌🏼 E4.0 person in bed: medium-light skin tone +1F6CC 1F3FD ; fully-qualified # 🛌🏽 E4.0 person in bed: medium skin tone +1F6CC 1F3FE ; fully-qualified # 🛌🏾 E4.0 person in bed: medium-dark skin tone +1F6CC 1F3FF ; fully-qualified # 🛌🏿 E4.0 person in bed: dark skin tone + +# subgroup: family +1F9D1 200D 1F91D 200D 1F9D1 ; fully-qualified # 🧑‍🤝‍🧑 E12.0 people holding hands +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏻‍🤝‍🧑🏻 E12.0 people holding hands: light skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍🤝‍🧑🏼 E12.1 people holding hands: light skin tone, medium-light skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍🤝‍🧑🏽 E12.1 people holding hands: light skin tone, medium skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍🤝‍🧑🏾 E12.1 people holding hands: light skin tone, medium-dark skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍🤝‍🧑🏿 E12.1 people holding hands: light skin tone, dark skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍🤝‍🧑🏻 E12.0 people holding hands: medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏼‍🤝‍🧑🏼 E12.0 people holding hands: medium-light skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍🤝‍🧑🏽 E12.1 people holding hands: medium-light skin tone, medium skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍🤝‍🧑🏾 E12.1 people holding hands: medium-light skin tone, medium-dark skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍🤝‍🧑🏿 E12.1 people holding hands: medium-light skin tone, dark skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍🤝‍🧑🏻 E12.0 people holding hands: medium skin tone, light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍🤝‍🧑🏼 E12.0 people holding hands: medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏽‍🤝‍🧑🏽 E12.0 people holding hands: medium skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍🤝‍🧑🏾 E12.1 people holding hands: medium skin tone, medium-dark skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍🤝‍🧑🏿 E12.1 people holding hands: medium skin tone, dark skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍🤝‍🧑🏻 E12.0 people holding hands: medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍🤝‍🧑🏼 E12.0 people holding hands: medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍🤝‍🧑🏽 E12.0 people holding hands: medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏾‍🤝‍🧑🏾 E12.0 people holding hands: medium-dark skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍🤝‍🧑🏿 E12.1 people holding hands: medium-dark skin tone, dark skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍🤝‍🧑🏻 E12.0 people holding hands: dark skin tone, light skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍🤝‍🧑🏼 E12.0 people holding hands: dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍🤝‍🧑🏽 E12.0 people holding hands: dark skin tone, medium skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍🤝‍🧑🏾 E12.0 people holding hands: dark skin tone, medium-dark skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏿‍🤝‍🧑🏿 E12.0 people holding hands: dark skin tone +1F46D ; fully-qualified # 👭 E1.0 women holding hands +1F46D 1F3FB ; fully-qualified # 👭🏻 E12.0 women holding hands: light skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍🤝‍👩🏼 E12.1 women holding hands: light skin tone, medium-light skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍🤝‍👩🏽 E12.1 women holding hands: light skin tone, medium skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍🤝‍👩🏾 E12.1 women holding hands: light skin tone, medium-dark skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍🤝‍👩🏿 E12.1 women holding hands: light skin tone, dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍🤝‍👩🏻 E12.0 women holding hands: medium-light skin tone, light skin tone +1F46D 1F3FC ; fully-qualified # 👭🏼 E12.0 women holding hands: medium-light skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍🤝‍👩🏽 E12.1 women holding hands: medium-light skin tone, medium skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍🤝‍👩🏾 E12.1 women holding hands: medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍🤝‍👩🏿 E12.1 women holding hands: medium-light skin tone, dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍🤝‍👩🏻 E12.0 women holding hands: medium skin tone, light skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍🤝‍👩🏼 E12.0 women holding hands: medium skin tone, medium-light skin tone +1F46D 1F3FD ; fully-qualified # 👭🏽 E12.0 women holding hands: medium skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍🤝‍👩🏾 E12.1 women holding hands: medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍🤝‍👩🏿 E12.1 women holding hands: medium skin tone, dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍🤝‍👩🏻 E12.0 women holding hands: medium-dark skin tone, light skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍🤝‍👩🏼 E12.0 women holding hands: medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍🤝‍👩🏽 E12.0 women holding hands: medium-dark skin tone, medium skin tone +1F46D 1F3FE ; fully-qualified # 👭🏾 E12.0 women holding hands: medium-dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍🤝‍👩🏿 E12.1 women holding hands: medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍🤝‍👩🏻 E12.0 women holding hands: dark skin tone, light skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍🤝‍👩🏼 E12.0 women holding hands: dark skin tone, medium-light skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍🤝‍👩🏽 E12.0 women holding hands: dark skin tone, medium skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍🤝‍👩🏾 E12.0 women holding hands: dark skin tone, medium-dark skin tone +1F46D 1F3FF ; fully-qualified # 👭🏿 E12.0 women holding hands: dark skin tone +1F46B ; fully-qualified # 👫 E0.6 woman and man holding hands +1F46B 1F3FB ; fully-qualified # 👫🏻 E12.0 woman and man holding hands: light skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍🤝‍👨🏼 E12.0 woman and man holding hands: light skin tone, medium-light skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍🤝‍👨🏽 E12.0 woman and man holding hands: light skin tone, medium skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍🤝‍👨🏾 E12.0 woman and man holding hands: light skin tone, medium-dark skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍🤝‍👨🏿 E12.0 woman and man holding hands: light skin tone, dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍🤝‍👨🏻 E12.0 woman and man holding hands: medium-light skin tone, light skin tone +1F46B 1F3FC ; fully-qualified # 👫🏼 E12.0 woman and man holding hands: medium-light skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍🤝‍👨🏽 E12.0 woman and man holding hands: medium-light skin tone, medium skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍🤝‍👨🏾 E12.0 woman and man holding hands: medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍🤝‍👨🏿 E12.0 woman and man holding hands: medium-light skin tone, dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍🤝‍👨🏻 E12.0 woman and man holding hands: medium skin tone, light skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍🤝‍👨🏼 E12.0 woman and man holding hands: medium skin tone, medium-light skin tone +1F46B 1F3FD ; fully-qualified # 👫🏽 E12.0 woman and man holding hands: medium skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍🤝‍👨🏾 E12.0 woman and man holding hands: medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍🤝‍👨🏿 E12.0 woman and man holding hands: medium skin tone, dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍🤝‍👨🏻 E12.0 woman and man holding hands: medium-dark skin tone, light skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍🤝‍👨🏼 E12.0 woman and man holding hands: medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍🤝‍👨🏽 E12.0 woman and man holding hands: medium-dark skin tone, medium skin tone +1F46B 1F3FE ; fully-qualified # 👫🏾 E12.0 woman and man holding hands: medium-dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍🤝‍👨🏿 E12.0 woman and man holding hands: medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍🤝‍👨🏻 E12.0 woman and man holding hands: dark skin tone, light skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍🤝‍👨🏼 E12.0 woman and man holding hands: dark skin tone, medium-light skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍🤝‍👨🏽 E12.0 woman and man holding hands: dark skin tone, medium skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍🤝‍👨🏾 E12.0 woman and man holding hands: dark skin tone, medium-dark skin tone +1F46B 1F3FF ; fully-qualified # 👫🏿 E12.0 woman and man holding hands: dark skin tone +1F46C ; fully-qualified # 👬 E1.0 men holding hands +1F46C 1F3FB ; fully-qualified # 👬🏻 E12.0 men holding hands: light skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍🤝‍👨🏼 E12.1 men holding hands: light skin tone, medium-light skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍🤝‍👨🏽 E12.1 men holding hands: light skin tone, medium skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍🤝‍👨🏾 E12.1 men holding hands: light skin tone, medium-dark skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍🤝‍👨🏿 E12.1 men holding hands: light skin tone, dark skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍🤝‍👨🏻 E12.0 men holding hands: medium-light skin tone, light skin tone +1F46C 1F3FC ; fully-qualified # 👬🏼 E12.0 men holding hands: medium-light skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍🤝‍👨🏽 E12.1 men holding hands: medium-light skin tone, medium skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍🤝‍👨🏾 E12.1 men holding hands: medium-light skin tone, medium-dark skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍🤝‍👨🏿 E12.1 men holding hands: medium-light skin tone, dark skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍🤝‍👨🏻 E12.0 men holding hands: medium skin tone, light skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍🤝‍👨🏼 E12.0 men holding hands: medium skin tone, medium-light skin tone +1F46C 1F3FD ; fully-qualified # 👬🏽 E12.0 men holding hands: medium skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍🤝‍👨🏾 E12.1 men holding hands: medium skin tone, medium-dark skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍🤝‍👨🏿 E12.1 men holding hands: medium skin tone, dark skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍🤝‍👨🏻 E12.0 men holding hands: medium-dark skin tone, light skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍🤝‍👨🏼 E12.0 men holding hands: medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍🤝‍👨🏽 E12.0 men holding hands: medium-dark skin tone, medium skin tone +1F46C 1F3FE ; fully-qualified # 👬🏾 E12.0 men holding hands: medium-dark skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍🤝‍👨🏿 E12.1 men holding hands: medium-dark skin tone, dark skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍🤝‍👨🏻 E12.0 men holding hands: dark skin tone, light skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍🤝‍👨🏼 E12.0 men holding hands: dark skin tone, medium-light skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍🤝‍👨🏽 E12.0 men holding hands: dark skin tone, medium skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍🤝‍👨🏾 E12.0 men holding hands: dark skin tone, medium-dark skin tone +1F46C 1F3FF ; fully-qualified # 👬🏿 E12.0 men holding hands: dark skin tone +1F48F ; fully-qualified # 💏 E0.6 kiss +1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 E2.0 kiss: woman, man +1F469 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👩‍❤‍💋‍👨 E2.0 kiss: woman, man +1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 E2.0 kiss: man, man +1F468 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👨‍❤‍💋‍👨 E2.0 kiss: man, man +1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 E2.0 kiss: woman, woman +1F469 200D 2764 200D 1F48B 200D 1F469 ; minimally-qualified # 👩‍❤‍💋‍👩 E2.0 kiss: woman, woman +1F491 ; fully-qualified # 💑 E0.6 couple with heart +1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 E2.0 couple with heart: woman, man +1F469 200D 2764 200D 1F468 ; minimally-qualified # 👩‍❤‍👨 E2.0 couple with heart: woman, man +1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 E2.0 couple with heart: man, man +1F468 200D 2764 200D 1F468 ; minimally-qualified # 👨‍❤‍👨 E2.0 couple with heart: man, man +1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 E2.0 couple with heart: woman, woman +1F469 200D 2764 200D 1F469 ; minimally-qualified # 👩‍❤‍👩 E2.0 couple with heart: woman, woman +1F46A ; fully-qualified # 👪 E0.6 family +1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 E2.0 family: man, woman, boy +1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 E2.0 family: man, woman, girl +1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 E2.0 family: man, woman, girl, boy +1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 E2.0 family: man, woman, boy, boy +1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 E2.0 family: man, woman, girl, girl +1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 E2.0 family: man, man, boy +1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 E2.0 family: man, man, girl +1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 E2.0 family: man, man, girl, boy +1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 E2.0 family: man, man, boy, boy +1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 E2.0 family: man, man, girl, girl +1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 E2.0 family: woman, woman, boy +1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 E2.0 family: woman, woman, girl +1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 E2.0 family: woman, woman, girl, boy +1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 E2.0 family: woman, woman, boy, boy +1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 E2.0 family: woman, woman, girl, girl +1F468 200D 1F466 ; fully-qualified # 👨‍👦 E4.0 family: man, boy +1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 E4.0 family: man, boy, boy +1F468 200D 1F467 ; fully-qualified # 👨‍👧 E4.0 family: man, girl +1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 E4.0 family: man, girl, boy +1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 E4.0 family: man, girl, girl +1F469 200D 1F466 ; fully-qualified # 👩‍👦 E4.0 family: woman, boy +1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 E4.0 family: woman, boy, boy +1F469 200D 1F467 ; fully-qualified # 👩‍👧 E4.0 family: woman, girl +1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 E4.0 family: woman, girl, boy +1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 E4.0 family: woman, girl, girl + +# subgroup: person-symbol +1F5E3 FE0F ; fully-qualified # 🗣️ E0.7 speaking head +1F5E3 ; unqualified # 🗣 E0.7 speaking head +1F464 ; fully-qualified # 👤 E0.6 bust in silhouette +1F465 ; fully-qualified # 👥 E1.0 busts in silhouette +1FAC2 ; fully-qualified # 🫂 E13.0 people hugging +1F463 ; fully-qualified # 👣 E0.6 footprints + +# People & Body subtotal: 2480 +# People & Body subtotal: 490 w/o modifiers + +# group: Component + +# subgroup: skin-tone +1F3FB ; component # 🏻 E1.0 light skin tone +1F3FC ; component # 🏼 E1.0 medium-light skin tone +1F3FD ; component # 🏽 E1.0 medium skin tone +1F3FE ; component # 🏾 E1.0 medium-dark skin tone +1F3FF ; component # 🏿 E1.0 dark skin tone + +# subgroup: hair-style +1F9B0 ; component # 🦰 E11.0 red hair +1F9B1 ; component # 🦱 E11.0 curly hair +1F9B3 ; component # 🦳 E11.0 white hair +1F9B2 ; component # 🦲 E11.0 bald + +# Component subtotal: 9 +# Component subtotal: 4 w/o modifiers + +# group: Animals & Nature + +# subgroup: animal-mammal +1F435 ; fully-qualified # 🐵 E0.6 monkey face +1F412 ; fully-qualified # 🐒 E0.6 monkey +1F98D ; fully-qualified # 🦍 E3.0 gorilla +1F9A7 ; fully-qualified # 🦧 E12.0 orangutan +1F436 ; fully-qualified # 🐶 E0.6 dog face +1F415 ; fully-qualified # 🐕 E0.7 dog +1F9AE ; fully-qualified # 🦮 E12.0 guide dog +1F415 200D 1F9BA ; fully-qualified # 🐕‍🦺 E12.0 service dog +1F429 ; fully-qualified # 🐩 E0.6 poodle +1F43A ; fully-qualified # 🐺 E0.6 wolf +1F98A ; fully-qualified # 🦊 E3.0 fox +1F99D ; fully-qualified # 🦝 E11.0 raccoon +1F431 ; fully-qualified # 🐱 E0.6 cat face +1F408 ; fully-qualified # 🐈 E0.7 cat +1F408 200D 2B1B ; fully-qualified # 🐈‍⬛ E13.0 black cat +1F981 ; fully-qualified # 🦁 E1.0 lion +1F42F ; fully-qualified # 🐯 E0.6 tiger face +1F405 ; fully-qualified # 🐅 E1.0 tiger +1F406 ; fully-qualified # 🐆 E1.0 leopard +1F434 ; fully-qualified # 🐴 E0.6 horse face +1F40E ; fully-qualified # 🐎 E0.6 horse +1F984 ; fully-qualified # 🦄 E1.0 unicorn +1F993 ; fully-qualified # 🦓 E5.0 zebra +1F98C ; fully-qualified # 🦌 E3.0 deer +1F9AC ; fully-qualified # 🦬 E13.0 bison +1F42E ; fully-qualified # 🐮 E0.6 cow face +1F402 ; fully-qualified # 🐂 E1.0 ox +1F403 ; fully-qualified # 🐃 E1.0 water buffalo +1F404 ; fully-qualified # 🐄 E1.0 cow +1F437 ; fully-qualified # 🐷 E0.6 pig face +1F416 ; fully-qualified # 🐖 E1.0 pig +1F417 ; fully-qualified # 🐗 E0.6 boar +1F43D ; fully-qualified # 🐽 E0.6 pig nose +1F40F ; fully-qualified # 🐏 E1.0 ram +1F411 ; fully-qualified # 🐑 E0.6 ewe +1F410 ; fully-qualified # 🐐 E1.0 goat +1F42A ; fully-qualified # 🐪 E1.0 camel +1F42B ; fully-qualified # 🐫 E0.6 two-hump camel +1F999 ; fully-qualified # 🦙 E11.0 llama +1F992 ; fully-qualified # 🦒 E5.0 giraffe +1F418 ; fully-qualified # 🐘 E0.6 elephant +1F9A3 ; fully-qualified # 🦣 E13.0 mammoth +1F98F ; fully-qualified # 🦏 E3.0 rhinoceros +1F99B ; fully-qualified # 🦛 E11.0 hippopotamus +1F42D ; fully-qualified # 🐭 E0.6 mouse face +1F401 ; fully-qualified # 🐁 E1.0 mouse +1F400 ; fully-qualified # 🐀 E1.0 rat +1F439 ; fully-qualified # 🐹 E0.6 hamster +1F430 ; fully-qualified # 🐰 E0.6 rabbit face +1F407 ; fully-qualified # 🐇 E1.0 rabbit +1F43F FE0F ; fully-qualified # 🐿️ E0.7 chipmunk +1F43F ; unqualified # 🐿 E0.7 chipmunk +1F9AB ; fully-qualified # 🦫 E13.0 beaver +1F994 ; fully-qualified # 🦔 E5.0 hedgehog +1F987 ; fully-qualified # 🦇 E3.0 bat +1F43B ; fully-qualified # 🐻 E0.6 bear +1F43B 200D 2744 FE0F ; fully-qualified # 🐻‍❄️ E13.0 polar bear +1F43B 200D 2744 ; minimally-qualified # 🐻‍❄ E13.0 polar bear +1F428 ; fully-qualified # 🐨 E0.6 koala +1F43C ; fully-qualified # 🐼 E0.6 panda +1F9A5 ; fully-qualified # 🦥 E12.0 sloth +1F9A6 ; fully-qualified # 🦦 E12.0 otter +1F9A8 ; fully-qualified # 🦨 E12.0 skunk +1F998 ; fully-qualified # 🦘 E11.0 kangaroo +1F9A1 ; fully-qualified # 🦡 E11.0 badger +1F43E ; fully-qualified # 🐾 E0.6 paw prints + +# subgroup: animal-bird +1F983 ; fully-qualified # 🦃 E1.0 turkey +1F414 ; fully-qualified # 🐔 E0.6 chicken +1F413 ; fully-qualified # 🐓 E1.0 rooster +1F423 ; fully-qualified # 🐣 E0.6 hatching chick +1F424 ; fully-qualified # 🐤 E0.6 baby chick +1F425 ; fully-qualified # 🐥 E0.6 front-facing baby chick +1F426 ; fully-qualified # 🐦 E0.6 bird +1F427 ; fully-qualified # 🐧 E0.6 penguin +1F54A FE0F ; fully-qualified # 🕊️ E0.7 dove +1F54A ; unqualified # 🕊 E0.7 dove +1F985 ; fully-qualified # 🦅 E3.0 eagle +1F986 ; fully-qualified # 🦆 E3.0 duck +1F9A2 ; fully-qualified # 🦢 E11.0 swan +1F989 ; fully-qualified # 🦉 E3.0 owl +1F9A4 ; fully-qualified # 🦤 E13.0 dodo +1FAB6 ; fully-qualified # 🪶 E13.0 feather +1F9A9 ; fully-qualified # 🦩 E12.0 flamingo +1F99A ; fully-qualified # 🦚 E11.0 peacock +1F99C ; fully-qualified # 🦜 E11.0 parrot + +# subgroup: animal-amphibian +1F438 ; fully-qualified # 🐸 E0.6 frog + +# subgroup: animal-reptile +1F40A ; fully-qualified # 🐊 E1.0 crocodile +1F422 ; fully-qualified # 🐢 E0.6 turtle +1F98E ; fully-qualified # 🦎 E3.0 lizard +1F40D ; fully-qualified # 🐍 E0.6 snake +1F432 ; fully-qualified # 🐲 E0.6 dragon face +1F409 ; fully-qualified # 🐉 E1.0 dragon +1F995 ; fully-qualified # 🦕 E5.0 sauropod +1F996 ; fully-qualified # 🦖 E5.0 T-Rex + +# subgroup: animal-marine +1F433 ; fully-qualified # 🐳 E0.6 spouting whale +1F40B ; fully-qualified # 🐋 E1.0 whale +1F42C ; fully-qualified # 🐬 E0.6 dolphin +1F9AD ; fully-qualified # 🦭 E13.0 seal +1F41F ; fully-qualified # 🐟 E0.6 fish +1F420 ; fully-qualified # 🐠 E0.6 tropical fish +1F421 ; fully-qualified # 🐡 E0.6 blowfish +1F988 ; fully-qualified # 🦈 E3.0 shark +1F419 ; fully-qualified # 🐙 E0.6 octopus +1F41A ; fully-qualified # 🐚 E0.6 spiral shell + +# subgroup: animal-bug +1F40C ; fully-qualified # 🐌 E0.6 snail +1F98B ; fully-qualified # 🦋 E3.0 butterfly +1F41B ; fully-qualified # 🐛 E0.6 bug +1F41C ; fully-qualified # 🐜 E0.6 ant +1F41D ; fully-qualified # 🐝 E0.6 honeybee +1FAB2 ; fully-qualified # 🪲 E13.0 beetle +1F41E ; fully-qualified # 🐞 E0.6 lady beetle +1F997 ; fully-qualified # 🦗 E5.0 cricket +1FAB3 ; fully-qualified # 🪳 E13.0 cockroach +1F577 FE0F ; fully-qualified # 🕷️ E0.7 spider +1F577 ; unqualified # 🕷 E0.7 spider +1F578 FE0F ; fully-qualified # 🕸️ E0.7 spider web +1F578 ; unqualified # 🕸 E0.7 spider web +1F982 ; fully-qualified # 🦂 E1.0 scorpion +1F99F ; fully-qualified # 🦟 E11.0 mosquito +1FAB0 ; fully-qualified # 🪰 E13.0 fly +1FAB1 ; fully-qualified # 🪱 E13.0 worm +1F9A0 ; fully-qualified # 🦠 E11.0 microbe + +# subgroup: plant-flower +1F490 ; fully-qualified # 💐 E0.6 bouquet +1F338 ; fully-qualified # 🌸 E0.6 cherry blossom +1F4AE ; fully-qualified # 💮 E0.6 white flower +1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette +1F3F5 ; unqualified # 🏵 E0.7 rosette +1F339 ; fully-qualified # 🌹 E0.6 rose +1F940 ; fully-qualified # 🥀 E3.0 wilted flower +1F33A ; fully-qualified # 🌺 E0.6 hibiscus +1F33B ; fully-qualified # 🌻 E0.6 sunflower +1F33C ; fully-qualified # 🌼 E0.6 blossom +1F337 ; fully-qualified # 🌷 E0.6 tulip + +# subgroup: plant-other +1F331 ; fully-qualified # 🌱 E0.6 seedling +1FAB4 ; fully-qualified # 🪴 E13.0 potted plant +1F332 ; fully-qualified # 🌲 E1.0 evergreen tree +1F333 ; fully-qualified # 🌳 E1.0 deciduous tree +1F334 ; fully-qualified # 🌴 E0.6 palm tree +1F335 ; fully-qualified # 🌵 E0.6 cactus +1F33E ; fully-qualified # 🌾 E0.6 sheaf of rice +1F33F ; fully-qualified # 🌿 E0.6 herb +2618 FE0F ; fully-qualified # ☘️ E1.0 shamrock +2618 ; unqualified # ☘ E1.0 shamrock +1F340 ; fully-qualified # 🍀 E0.6 four leaf clover +1F341 ; fully-qualified # 🍁 E0.6 maple leaf +1F342 ; fully-qualified # 🍂 E0.6 fallen leaf +1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind + +# Animals & Nature subtotal: 147 +# Animals & Nature subtotal: 147 w/o modifiers + +# group: Food & Drink + +# subgroup: food-fruit +1F347 ; fully-qualified # 🍇 E0.6 grapes +1F348 ; fully-qualified # 🍈 E0.6 melon +1F349 ; fully-qualified # 🍉 E0.6 watermelon +1F34A ; fully-qualified # 🍊 E0.6 tangerine +1F34B ; fully-qualified # 🍋 E1.0 lemon +1F34C ; fully-qualified # 🍌 E0.6 banana +1F34D ; fully-qualified # 🍍 E0.6 pineapple +1F96D ; fully-qualified # 🥭 E11.0 mango +1F34E ; fully-qualified # 🍎 E0.6 red apple +1F34F ; fully-qualified # 🍏 E0.6 green apple +1F350 ; fully-qualified # 🍐 E1.0 pear +1F351 ; fully-qualified # 🍑 E0.6 peach +1F352 ; fully-qualified # 🍒 E0.6 cherries +1F353 ; fully-qualified # 🍓 E0.6 strawberry +1FAD0 ; fully-qualified # 🫐 E13.0 blueberries +1F95D ; fully-qualified # 🥝 E3.0 kiwi fruit +1F345 ; fully-qualified # 🍅 E0.6 tomato +1FAD2 ; fully-qualified # 🫒 E13.0 olive +1F965 ; fully-qualified # 🥥 E5.0 coconut + +# subgroup: food-vegetable +1F951 ; fully-qualified # 🥑 E3.0 avocado +1F346 ; fully-qualified # 🍆 E0.6 eggplant +1F954 ; fully-qualified # 🥔 E3.0 potato +1F955 ; fully-qualified # 🥕 E3.0 carrot +1F33D ; fully-qualified # 🌽 E0.6 ear of corn +1F336 FE0F ; fully-qualified # 🌶️ E0.7 hot pepper +1F336 ; unqualified # 🌶 E0.7 hot pepper +1FAD1 ; fully-qualified # 🫑 E13.0 bell pepper +1F952 ; fully-qualified # 🥒 E3.0 cucumber +1F96C ; fully-qualified # 🥬 E11.0 leafy green +1F966 ; fully-qualified # 🥦 E5.0 broccoli +1F9C4 ; fully-qualified # 🧄 E12.0 garlic +1F9C5 ; fully-qualified # 🧅 E12.0 onion +1F344 ; fully-qualified # 🍄 E0.6 mushroom +1F95C ; fully-qualified # 🥜 E3.0 peanuts +1F330 ; fully-qualified # 🌰 E0.6 chestnut + +# subgroup: food-prepared +1F35E ; fully-qualified # 🍞 E0.6 bread +1F950 ; fully-qualified # 🥐 E3.0 croissant +1F956 ; fully-qualified # 🥖 E3.0 baguette bread +1FAD3 ; fully-qualified # 🫓 E13.0 flatbread +1F968 ; fully-qualified # 🥨 E5.0 pretzel +1F96F ; fully-qualified # 🥯 E11.0 bagel +1F95E ; fully-qualified # 🥞 E3.0 pancakes +1F9C7 ; fully-qualified # 🧇 E12.0 waffle +1F9C0 ; fully-qualified # 🧀 E1.0 cheese wedge +1F356 ; fully-qualified # 🍖 E0.6 meat on bone +1F357 ; fully-qualified # 🍗 E0.6 poultry leg +1F969 ; fully-qualified # 🥩 E5.0 cut of meat +1F953 ; fully-qualified # 🥓 E3.0 bacon +1F354 ; fully-qualified # 🍔 E0.6 hamburger +1F35F ; fully-qualified # 🍟 E0.6 french fries +1F355 ; fully-qualified # 🍕 E0.6 pizza +1F32D ; fully-qualified # 🌭 E1.0 hot dog +1F96A ; fully-qualified # 🥪 E5.0 sandwich +1F32E ; fully-qualified # 🌮 E1.0 taco +1F32F ; fully-qualified # 🌯 E1.0 burrito +1FAD4 ; fully-qualified # 🫔 E13.0 tamale +1F959 ; fully-qualified # 🥙 E3.0 stuffed flatbread +1F9C6 ; fully-qualified # 🧆 E12.0 falafel +1F95A ; fully-qualified # 🥚 E3.0 egg +1F373 ; fully-qualified # 🍳 E0.6 cooking +1F958 ; fully-qualified # 🥘 E3.0 shallow pan of food +1F372 ; fully-qualified # 🍲 E0.6 pot of food +1FAD5 ; fully-qualified # 🫕 E13.0 fondue +1F963 ; fully-qualified # 🥣 E5.0 bowl with spoon +1F957 ; fully-qualified # 🥗 E3.0 green salad +1F37F ; fully-qualified # 🍿 E1.0 popcorn +1F9C8 ; fully-qualified # 🧈 E12.0 butter +1F9C2 ; fully-qualified # 🧂 E11.0 salt +1F96B ; fully-qualified # 🥫 E5.0 canned food + +# subgroup: food-asian +1F371 ; fully-qualified # 🍱 E0.6 bento box +1F358 ; fully-qualified # 🍘 E0.6 rice cracker +1F359 ; fully-qualified # 🍙 E0.6 rice ball +1F35A ; fully-qualified # 🍚 E0.6 cooked rice +1F35B ; fully-qualified # 🍛 E0.6 curry rice +1F35C ; fully-qualified # 🍜 E0.6 steaming bowl +1F35D ; fully-qualified # 🍝 E0.6 spaghetti +1F360 ; fully-qualified # 🍠 E0.6 roasted sweet potato +1F362 ; fully-qualified # 🍢 E0.6 oden +1F363 ; fully-qualified # 🍣 E0.6 sushi +1F364 ; fully-qualified # 🍤 E0.6 fried shrimp +1F365 ; fully-qualified # 🍥 E0.6 fish cake with swirl +1F96E ; fully-qualified # 🥮 E11.0 moon cake +1F361 ; fully-qualified # 🍡 E0.6 dango +1F95F ; fully-qualified # 🥟 E5.0 dumpling +1F960 ; fully-qualified # 🥠 E5.0 fortune cookie +1F961 ; fully-qualified # 🥡 E5.0 takeout box + +# subgroup: food-marine +1F980 ; fully-qualified # 🦀 E1.0 crab +1F99E ; fully-qualified # 🦞 E11.0 lobster +1F990 ; fully-qualified # 🦐 E3.0 shrimp +1F991 ; fully-qualified # 🦑 E3.0 squid +1F9AA ; fully-qualified # 🦪 E12.0 oyster + +# subgroup: food-sweet +1F366 ; fully-qualified # 🍦 E0.6 soft ice cream +1F367 ; fully-qualified # 🍧 E0.6 shaved ice +1F368 ; fully-qualified # 🍨 E0.6 ice cream +1F369 ; fully-qualified # 🍩 E0.6 doughnut +1F36A ; fully-qualified # 🍪 E0.6 cookie +1F382 ; fully-qualified # 🎂 E0.6 birthday cake +1F370 ; fully-qualified # 🍰 E0.6 shortcake +1F9C1 ; fully-qualified # 🧁 E11.0 cupcake +1F967 ; fully-qualified # 🥧 E5.0 pie +1F36B ; fully-qualified # 🍫 E0.6 chocolate bar +1F36C ; fully-qualified # 🍬 E0.6 candy +1F36D ; fully-qualified # 🍭 E0.6 lollipop +1F36E ; fully-qualified # 🍮 E0.6 custard +1F36F ; fully-qualified # 🍯 E0.6 honey pot + +# subgroup: drink +1F37C ; fully-qualified # 🍼 E1.0 baby bottle +1F95B ; fully-qualified # 🥛 E3.0 glass of milk +2615 ; fully-qualified # ☕ E0.6 hot beverage +1FAD6 ; fully-qualified # 🫖 E13.0 teapot +1F375 ; fully-qualified # 🍵 E0.6 teacup without handle +1F376 ; fully-qualified # 🍶 E0.6 sake +1F37E ; fully-qualified # 🍾 E1.0 bottle with popping cork +1F377 ; fully-qualified # 🍷 E0.6 wine glass +1F378 ; fully-qualified # 🍸 E0.6 cocktail glass +1F379 ; fully-qualified # 🍹 E0.6 tropical drink +1F37A ; fully-qualified # 🍺 E0.6 beer mug +1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs +1F942 ; fully-qualified # 🥂 E3.0 clinking glasses +1F943 ; fully-qualified # 🥃 E3.0 tumbler glass +1F964 ; fully-qualified # 🥤 E5.0 cup with straw +1F9CB ; fully-qualified # 🧋 E13.0 bubble tea +1F9C3 ; fully-qualified # 🧃 E12.0 beverage box +1F9C9 ; fully-qualified # 🧉 E12.0 mate +1F9CA ; fully-qualified # 🧊 E12.0 ice + +# subgroup: dishware +1F962 ; fully-qualified # 🥢 E5.0 chopsticks +1F37D FE0F ; fully-qualified # 🍽️ E0.7 fork and knife with plate +1F37D ; unqualified # 🍽 E0.7 fork and knife with plate +1F374 ; fully-qualified # 🍴 E0.6 fork and knife +1F944 ; fully-qualified # 🥄 E3.0 spoon +1F52A ; fully-qualified # 🔪 E0.6 kitchen knife +1F3FA ; fully-qualified # 🏺 E1.0 amphora + +# Food & Drink subtotal: 131 +# Food & Drink subtotal: 131 w/o modifiers + +# group: Travel & Places + +# subgroup: place-map +1F30D ; fully-qualified # 🌍 E0.7 globe showing Europe-Africa +1F30E ; fully-qualified # 🌎 E0.7 globe showing Americas +1F30F ; fully-qualified # 🌏 E0.6 globe showing Asia-Australia +1F310 ; fully-qualified # 🌐 E1.0 globe with meridians +1F5FA FE0F ; fully-qualified # 🗺️ E0.7 world map +1F5FA ; unqualified # 🗺 E0.7 world map +1F5FE ; fully-qualified # 🗾 E0.6 map of Japan +1F9ED ; fully-qualified # 🧭 E11.0 compass + +# subgroup: place-geographic +1F3D4 FE0F ; fully-qualified # 🏔️ E0.7 snow-capped mountain +1F3D4 ; unqualified # 🏔 E0.7 snow-capped mountain +26F0 FE0F ; fully-qualified # ⛰️ E0.7 mountain +26F0 ; unqualified # ⛰ E0.7 mountain +1F30B ; fully-qualified # 🌋 E0.6 volcano +1F5FB ; fully-qualified # 🗻 E0.6 mount fuji +1F3D5 FE0F ; fully-qualified # 🏕️ E0.7 camping +1F3D5 ; unqualified # 🏕 E0.7 camping +1F3D6 FE0F ; fully-qualified # 🏖️ E0.7 beach with umbrella +1F3D6 ; unqualified # 🏖 E0.7 beach with umbrella +1F3DC FE0F ; fully-qualified # 🏜️ E0.7 desert +1F3DC ; unqualified # 🏜 E0.7 desert +1F3DD FE0F ; fully-qualified # 🏝️ E0.7 desert island +1F3DD ; unqualified # 🏝 E0.7 desert island +1F3DE FE0F ; fully-qualified # 🏞️ E0.7 national park +1F3DE ; unqualified # 🏞 E0.7 national park + +# subgroup: place-building +1F3DF FE0F ; fully-qualified # 🏟️ E0.7 stadium +1F3DF ; unqualified # 🏟 E0.7 stadium +1F3DB FE0F ; fully-qualified # 🏛️ E0.7 classical building +1F3DB ; unqualified # 🏛 E0.7 classical building +1F3D7 FE0F ; fully-qualified # 🏗️ E0.7 building construction +1F3D7 ; unqualified # 🏗 E0.7 building construction +1F9F1 ; fully-qualified # 🧱 E11.0 brick +1FAA8 ; fully-qualified # 🪨 E13.0 rock +1FAB5 ; fully-qualified # 🪵 E13.0 wood +1F6D6 ; fully-qualified # 🛖 E13.0 hut +1F3D8 FE0F ; fully-qualified # 🏘️ E0.7 houses +1F3D8 ; unqualified # 🏘 E0.7 houses +1F3DA FE0F ; fully-qualified # 🏚️ E0.7 derelict house +1F3DA ; unqualified # 🏚 E0.7 derelict house +1F3E0 ; fully-qualified # 🏠 E0.6 house +1F3E1 ; fully-qualified # 🏡 E0.6 house with garden +1F3E2 ; fully-qualified # 🏢 E0.6 office building +1F3E3 ; fully-qualified # 🏣 E0.6 Japanese post office +1F3E4 ; fully-qualified # 🏤 E1.0 post office +1F3E5 ; fully-qualified # 🏥 E0.6 hospital +1F3E6 ; fully-qualified # 🏦 E0.6 bank +1F3E8 ; fully-qualified # 🏨 E0.6 hotel +1F3E9 ; fully-qualified # 🏩 E0.6 love hotel +1F3EA ; fully-qualified # 🏪 E0.6 convenience store +1F3EB ; fully-qualified # 🏫 E0.6 school +1F3EC ; fully-qualified # 🏬 E0.6 department store +1F3ED ; fully-qualified # 🏭 E0.6 factory +1F3EF ; fully-qualified # 🏯 E0.6 Japanese castle +1F3F0 ; fully-qualified # 🏰 E0.6 castle +1F492 ; fully-qualified # 💒 E0.6 wedding +1F5FC ; fully-qualified # 🗼 E0.6 Tokyo tower +1F5FD ; fully-qualified # 🗽 E0.6 Statue of Liberty + +# subgroup: place-religious +26EA ; fully-qualified # ⛪ E0.6 church +1F54C ; fully-qualified # 🕌 E1.0 mosque +1F6D5 ; fully-qualified # 🛕 E12.0 hindu temple +1F54D ; fully-qualified # 🕍 E1.0 synagogue +26E9 FE0F ; fully-qualified # ⛩️ E0.7 shinto shrine +26E9 ; unqualified # ⛩ E0.7 shinto shrine +1F54B ; fully-qualified # 🕋 E1.0 kaaba + +# subgroup: place-other +26F2 ; fully-qualified # ⛲ E0.6 fountain +26FA ; fully-qualified # ⛺ E0.6 tent +1F301 ; fully-qualified # 🌁 E0.6 foggy +1F303 ; fully-qualified # 🌃 E0.6 night with stars +1F3D9 FE0F ; fully-qualified # 🏙️ E0.7 cityscape +1F3D9 ; unqualified # 🏙 E0.7 cityscape +1F304 ; fully-qualified # 🌄 E0.6 sunrise over mountains +1F305 ; fully-qualified # 🌅 E0.6 sunrise +1F306 ; fully-qualified # 🌆 E0.6 cityscape at dusk +1F307 ; fully-qualified # 🌇 E0.6 sunset +1F309 ; fully-qualified # 🌉 E0.6 bridge at night +2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs +2668 ; unqualified # ♨ E0.6 hot springs +1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse +1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel +1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster +1F488 ; fully-qualified # 💈 E0.6 barber pole +1F3AA ; fully-qualified # 🎪 E0.6 circus tent + +# subgroup: transport-ground +1F682 ; fully-qualified # 🚂 E1.0 locomotive +1F683 ; fully-qualified # 🚃 E0.6 railway car +1F684 ; fully-qualified # 🚄 E0.6 high-speed train +1F685 ; fully-qualified # 🚅 E0.6 bullet train +1F686 ; fully-qualified # 🚆 E1.0 train +1F687 ; fully-qualified # 🚇 E0.6 metro +1F688 ; fully-qualified # 🚈 E1.0 light rail +1F689 ; fully-qualified # 🚉 E0.6 station +1F68A ; fully-qualified # 🚊 E1.0 tram +1F69D ; fully-qualified # 🚝 E1.0 monorail +1F69E ; fully-qualified # 🚞 E1.0 mountain railway +1F68B ; fully-qualified # 🚋 E1.0 tram car +1F68C ; fully-qualified # 🚌 E0.6 bus +1F68D ; fully-qualified # 🚍 E0.7 oncoming bus +1F68E ; fully-qualified # 🚎 E1.0 trolleybus +1F690 ; fully-qualified # 🚐 E1.0 minibus +1F691 ; fully-qualified # 🚑 E0.6 ambulance +1F692 ; fully-qualified # 🚒 E0.6 fire engine +1F693 ; fully-qualified # 🚓 E0.6 police car +1F694 ; fully-qualified # 🚔 E0.7 oncoming police car +1F695 ; fully-qualified # 🚕 E0.6 taxi +1F696 ; fully-qualified # 🚖 E1.0 oncoming taxi +1F697 ; fully-qualified # 🚗 E0.6 automobile +1F698 ; fully-qualified # 🚘 E0.7 oncoming automobile +1F699 ; fully-qualified # 🚙 E0.6 sport utility vehicle +1F6FB ; fully-qualified # 🛻 E13.0 pickup truck +1F69A ; fully-qualified # 🚚 E0.6 delivery truck +1F69B ; fully-qualified # 🚛 E1.0 articulated lorry +1F69C ; fully-qualified # 🚜 E1.0 tractor +1F3CE FE0F ; fully-qualified # 🏎️ E0.7 racing car +1F3CE ; unqualified # 🏎 E0.7 racing car +1F3CD FE0F ; fully-qualified # 🏍️ E0.7 motorcycle +1F3CD ; unqualified # 🏍 E0.7 motorcycle +1F6F5 ; fully-qualified # 🛵 E3.0 motor scooter +1F9BD ; fully-qualified # 🦽 E12.0 manual wheelchair +1F9BC ; fully-qualified # 🦼 E12.0 motorized wheelchair +1F6FA ; fully-qualified # 🛺 E12.0 auto rickshaw +1F6B2 ; fully-qualified # 🚲 E0.6 bicycle +1F6F4 ; fully-qualified # 🛴 E3.0 kick scooter +1F6F9 ; fully-qualified # 🛹 E11.0 skateboard +1F6FC ; fully-qualified # 🛼 E13.0 roller skate +1F68F ; fully-qualified # 🚏 E0.6 bus stop +1F6E3 FE0F ; fully-qualified # 🛣️ E0.7 motorway +1F6E3 ; unqualified # 🛣 E0.7 motorway +1F6E4 FE0F ; fully-qualified # 🛤️ E0.7 railway track +1F6E4 ; unqualified # 🛤 E0.7 railway track +1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum +1F6E2 ; unqualified # 🛢 E0.7 oil drum +26FD ; fully-qualified # ⛽ E0.6 fuel pump +1F6A8 ; fully-qualified # 🚨 E0.6 police car light +1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light +1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light +1F6D1 ; fully-qualified # 🛑 E3.0 stop sign +1F6A7 ; fully-qualified # 🚧 E0.6 construction + +# subgroup: transport-water +2693 ; fully-qualified # ⚓ E0.6 anchor +26F5 ; fully-qualified # ⛵ E0.6 sailboat +1F6F6 ; fully-qualified # 🛶 E3.0 canoe +1F6A4 ; fully-qualified # 🚤 E0.6 speedboat +1F6F3 FE0F ; fully-qualified # 🛳️ E0.7 passenger ship +1F6F3 ; unqualified # 🛳 E0.7 passenger ship +26F4 FE0F ; fully-qualified # ⛴️ E0.7 ferry +26F4 ; unqualified # ⛴ E0.7 ferry +1F6E5 FE0F ; fully-qualified # 🛥️ E0.7 motor boat +1F6E5 ; unqualified # 🛥 E0.7 motor boat +1F6A2 ; fully-qualified # 🚢 E0.6 ship + +# subgroup: transport-air +2708 FE0F ; fully-qualified # ✈️ E0.6 airplane +2708 ; unqualified # ✈ E0.6 airplane +1F6E9 FE0F ; fully-qualified # 🛩️ E0.7 small airplane +1F6E9 ; unqualified # 🛩 E0.7 small airplane +1F6EB ; fully-qualified # 🛫 E1.0 airplane departure +1F6EC ; fully-qualified # 🛬 E1.0 airplane arrival +1FA82 ; fully-qualified # 🪂 E12.0 parachute +1F4BA ; fully-qualified # 💺 E0.6 seat +1F681 ; fully-qualified # 🚁 E1.0 helicopter +1F69F ; fully-qualified # 🚟 E1.0 suspension railway +1F6A0 ; fully-qualified # 🚠 E1.0 mountain cableway +1F6A1 ; fully-qualified # 🚡 E1.0 aerial tramway +1F6F0 FE0F ; fully-qualified # 🛰️ E0.7 satellite +1F6F0 ; unqualified # 🛰 E0.7 satellite +1F680 ; fully-qualified # 🚀 E0.6 rocket +1F6F8 ; fully-qualified # 🛸 E5.0 flying saucer + +# subgroup: hotel +1F6CE FE0F ; fully-qualified # 🛎️ E0.7 bellhop bell +1F6CE ; unqualified # 🛎 E0.7 bellhop bell +1F9F3 ; fully-qualified # 🧳 E11.0 luggage + +# subgroup: time +231B ; fully-qualified # ⌛ E0.6 hourglass done +23F3 ; fully-qualified # ⏳ E0.6 hourglass not done +231A ; fully-qualified # ⌚ E0.6 watch +23F0 ; fully-qualified # ⏰ E0.6 alarm clock +23F1 FE0F ; fully-qualified # ⏱️ E1.0 stopwatch +23F1 ; unqualified # ⏱ E1.0 stopwatch +23F2 FE0F ; fully-qualified # ⏲️ E1.0 timer clock +23F2 ; unqualified # ⏲ E1.0 timer clock +1F570 FE0F ; fully-qualified # 🕰️ E0.7 mantelpiece clock +1F570 ; unqualified # 🕰 E0.7 mantelpiece clock +1F55B ; fully-qualified # 🕛 E0.6 twelve o’clock +1F567 ; fully-qualified # 🕧 E0.7 twelve-thirty +1F550 ; fully-qualified # 🕐 E0.6 one o’clock +1F55C ; fully-qualified # 🕜 E0.7 one-thirty +1F551 ; fully-qualified # 🕑 E0.6 two o’clock +1F55D ; fully-qualified # 🕝 E0.7 two-thirty +1F552 ; fully-qualified # 🕒 E0.6 three o’clock +1F55E ; fully-qualified # 🕞 E0.7 three-thirty +1F553 ; fully-qualified # 🕓 E0.6 four o’clock +1F55F ; fully-qualified # 🕟 E0.7 four-thirty +1F554 ; fully-qualified # 🕔 E0.6 five o’clock +1F560 ; fully-qualified # 🕠 E0.7 five-thirty +1F555 ; fully-qualified # 🕕 E0.6 six o’clock +1F561 ; fully-qualified # 🕡 E0.7 six-thirty +1F556 ; fully-qualified # 🕖 E0.6 seven o’clock +1F562 ; fully-qualified # 🕢 E0.7 seven-thirty +1F557 ; fully-qualified # 🕗 E0.6 eight o’clock +1F563 ; fully-qualified # 🕣 E0.7 eight-thirty +1F558 ; fully-qualified # 🕘 E0.6 nine o’clock +1F564 ; fully-qualified # 🕤 E0.7 nine-thirty +1F559 ; fully-qualified # 🕙 E0.6 ten o’clock +1F565 ; fully-qualified # 🕥 E0.7 ten-thirty +1F55A ; fully-qualified # 🕚 E0.6 eleven o’clock +1F566 ; fully-qualified # 🕦 E0.7 eleven-thirty + +# subgroup: sky & weather +1F311 ; fully-qualified # 🌑 E0.6 new moon +1F312 ; fully-qualified # 🌒 E1.0 waxing crescent moon +1F313 ; fully-qualified # 🌓 E0.6 first quarter moon +1F314 ; fully-qualified # 🌔 E0.6 waxing gibbous moon +1F315 ; fully-qualified # 🌕 E0.6 full moon +1F316 ; fully-qualified # 🌖 E1.0 waning gibbous moon +1F317 ; fully-qualified # 🌗 E1.0 last quarter moon +1F318 ; fully-qualified # 🌘 E1.0 waning crescent moon +1F319 ; fully-qualified # 🌙 E0.6 crescent moon +1F31A ; fully-qualified # 🌚 E1.0 new moon face +1F31B ; fully-qualified # 🌛 E0.6 first quarter moon face +1F31C ; fully-qualified # 🌜 E0.7 last quarter moon face +1F321 FE0F ; fully-qualified # 🌡️ E0.7 thermometer +1F321 ; unqualified # 🌡 E0.7 thermometer +2600 FE0F ; fully-qualified # ☀️ E0.6 sun +2600 ; unqualified # ☀ E0.6 sun +1F31D ; fully-qualified # 🌝 E1.0 full moon face +1F31E ; fully-qualified # 🌞 E1.0 sun with face +1FA90 ; fully-qualified # 🪐 E12.0 ringed planet +2B50 ; fully-qualified # ⭐ E0.6 star +1F31F ; fully-qualified # 🌟 E0.6 glowing star +1F320 ; fully-qualified # 🌠 E0.6 shooting star +1F30C ; fully-qualified # 🌌 E0.6 milky way +2601 FE0F ; fully-qualified # ☁️ E0.6 cloud +2601 ; unqualified # ☁ E0.6 cloud +26C5 ; fully-qualified # ⛅ E0.6 sun behind cloud +26C8 FE0F ; fully-qualified # ⛈️ E0.7 cloud with lightning and rain +26C8 ; unqualified # ⛈ E0.7 cloud with lightning and rain +1F324 FE0F ; fully-qualified # 🌤️ E0.7 sun behind small cloud +1F324 ; unqualified # 🌤 E0.7 sun behind small cloud +1F325 FE0F ; fully-qualified # 🌥️ E0.7 sun behind large cloud +1F325 ; unqualified # 🌥 E0.7 sun behind large cloud +1F326 FE0F ; fully-qualified # 🌦️ E0.7 sun behind rain cloud +1F326 ; unqualified # 🌦 E0.7 sun behind rain cloud +1F327 FE0F ; fully-qualified # 🌧️ E0.7 cloud with rain +1F327 ; unqualified # 🌧 E0.7 cloud with rain +1F328 FE0F ; fully-qualified # 🌨️ E0.7 cloud with snow +1F328 ; unqualified # 🌨 E0.7 cloud with snow +1F329 FE0F ; fully-qualified # 🌩️ E0.7 cloud with lightning +1F329 ; unqualified # 🌩 E0.7 cloud with lightning +1F32A FE0F ; fully-qualified # 🌪️ E0.7 tornado +1F32A ; unqualified # 🌪 E0.7 tornado +1F32B FE0F ; fully-qualified # 🌫️ E0.7 fog +1F32B ; unqualified # 🌫 E0.7 fog +1F32C FE0F ; fully-qualified # 🌬️ E0.7 wind face +1F32C ; unqualified # 🌬 E0.7 wind face +1F300 ; fully-qualified # 🌀 E0.6 cyclone +1F308 ; fully-qualified # 🌈 E0.6 rainbow +1F302 ; fully-qualified # 🌂 E0.6 closed umbrella +2602 FE0F ; fully-qualified # ☂️ E0.7 umbrella +2602 ; unqualified # ☂ E0.7 umbrella +2614 ; fully-qualified # ☔ E0.6 umbrella with rain drops +26F1 FE0F ; fully-qualified # ⛱️ E0.7 umbrella on ground +26F1 ; unqualified # ⛱ E0.7 umbrella on ground +26A1 ; fully-qualified # ⚡ E0.6 high voltage +2744 FE0F ; fully-qualified # ❄️ E0.6 snowflake +2744 ; unqualified # ❄ E0.6 snowflake +2603 FE0F ; fully-qualified # ☃️ E0.7 snowman +2603 ; unqualified # ☃ E0.7 snowman +26C4 ; fully-qualified # ⛄ E0.6 snowman without snow +2604 FE0F ; fully-qualified # ☄️ E1.0 comet +2604 ; unqualified # ☄ E1.0 comet +1F525 ; fully-qualified # 🔥 E0.6 fire +1F4A7 ; fully-qualified # 💧 E0.6 droplet +1F30A ; fully-qualified # 🌊 E0.6 water wave + +# Travel & Places subtotal: 264 +# Travel & Places subtotal: 264 w/o modifiers + +# group: Activities + +# subgroup: event +1F383 ; fully-qualified # 🎃 E0.6 jack-o-lantern +1F384 ; fully-qualified # 🎄 E0.6 Christmas tree +1F386 ; fully-qualified # 🎆 E0.6 fireworks +1F387 ; fully-qualified # 🎇 E0.6 sparkler +1F9E8 ; fully-qualified # 🧨 E11.0 firecracker +2728 ; fully-qualified # ✨ E0.6 sparkles +1F388 ; fully-qualified # 🎈 E0.6 balloon +1F389 ; fully-qualified # 🎉 E0.6 party popper +1F38A ; fully-qualified # 🎊 E0.6 confetti ball +1F38B ; fully-qualified # 🎋 E0.6 tanabata tree +1F38D ; fully-qualified # 🎍 E0.6 pine decoration +1F38E ; fully-qualified # 🎎 E0.6 Japanese dolls +1F38F ; fully-qualified # 🎏 E0.6 carp streamer +1F390 ; fully-qualified # 🎐 E0.6 wind chime +1F391 ; fully-qualified # 🎑 E0.6 moon viewing ceremony +1F9E7 ; fully-qualified # 🧧 E11.0 red envelope +1F380 ; fully-qualified # 🎀 E0.6 ribbon +1F381 ; fully-qualified # 🎁 E0.6 wrapped gift +1F397 FE0F ; fully-qualified # 🎗️ E0.7 reminder ribbon +1F397 ; unqualified # 🎗 E0.7 reminder ribbon +1F39F FE0F ; fully-qualified # 🎟️ E0.7 admission tickets +1F39F ; unqualified # 🎟 E0.7 admission tickets +1F3AB ; fully-qualified # 🎫 E0.6 ticket + +# subgroup: award-medal +1F396 FE0F ; fully-qualified # 🎖️ E0.7 military medal +1F396 ; unqualified # 🎖 E0.7 military medal +1F3C6 ; fully-qualified # 🏆 E0.6 trophy +1F3C5 ; fully-qualified # 🏅 E1.0 sports medal +1F947 ; fully-qualified # 🥇 E3.0 1st place medal +1F948 ; fully-qualified # 🥈 E3.0 2nd place medal +1F949 ; fully-qualified # 🥉 E3.0 3rd place medal + +# subgroup: sport +26BD ; fully-qualified # ⚽ E0.6 soccer ball +26BE ; fully-qualified # ⚾ E0.6 baseball +1F94E ; fully-qualified # 🥎 E11.0 softball +1F3C0 ; fully-qualified # 🏀 E0.6 basketball +1F3D0 ; fully-qualified # 🏐 E1.0 volleyball +1F3C8 ; fully-qualified # 🏈 E0.6 american football +1F3C9 ; fully-qualified # 🏉 E1.0 rugby football +1F3BE ; fully-qualified # 🎾 E0.6 tennis +1F94F ; fully-qualified # 🥏 E11.0 flying disc +1F3B3 ; fully-qualified # 🎳 E0.6 bowling +1F3CF ; fully-qualified # 🏏 E1.0 cricket game +1F3D1 ; fully-qualified # 🏑 E1.0 field hockey +1F3D2 ; fully-qualified # 🏒 E1.0 ice hockey +1F94D ; fully-qualified # 🥍 E11.0 lacrosse +1F3D3 ; fully-qualified # 🏓 E1.0 ping pong +1F3F8 ; fully-qualified # 🏸 E1.0 badminton +1F94A ; fully-qualified # 🥊 E3.0 boxing glove +1F94B ; fully-qualified # 🥋 E3.0 martial arts uniform +1F945 ; fully-qualified # 🥅 E3.0 goal net +26F3 ; fully-qualified # ⛳ E0.6 flag in hole +26F8 FE0F ; fully-qualified # ⛸️ E0.7 ice skate +26F8 ; unqualified # ⛸ E0.7 ice skate +1F3A3 ; fully-qualified # 🎣 E0.6 fishing pole +1F93F ; fully-qualified # 🤿 E12.0 diving mask +1F3BD ; fully-qualified # 🎽 E0.6 running shirt +1F3BF ; fully-qualified # 🎿 E0.6 skis +1F6F7 ; fully-qualified # 🛷 E5.0 sled +1F94C ; fully-qualified # 🥌 E5.0 curling stone + +# subgroup: game +1F3AF ; fully-qualified # 🎯 E0.6 direct hit +1FA80 ; fully-qualified # 🪀 E12.0 yo-yo +1FA81 ; fully-qualified # 🪁 E12.0 kite +1F3B1 ; fully-qualified # 🎱 E0.6 pool 8 ball +1F52E ; fully-qualified # 🔮 E0.6 crystal ball +1FA84 ; fully-qualified # 🪄 E13.0 magic wand +1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet +1F3AE ; fully-qualified # 🎮 E0.6 video game +1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick +1F579 ; unqualified # 🕹 E0.7 joystick +1F3B0 ; fully-qualified # 🎰 E0.6 slot machine +1F3B2 ; fully-qualified # 🎲 E0.6 game die +1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece +1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear +1FA85 ; fully-qualified # 🪅 E13.0 piñata +1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls +2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit +2660 ; unqualified # ♠ E0.6 spade suit +2665 FE0F ; fully-qualified # ♥️ E0.6 heart suit +2665 ; unqualified # ♥ E0.6 heart suit +2666 FE0F ; fully-qualified # ♦️ E0.6 diamond suit +2666 ; unqualified # ♦ E0.6 diamond suit +2663 FE0F ; fully-qualified # ♣️ E0.6 club suit +2663 ; unqualified # ♣ E0.6 club suit +265F FE0F ; fully-qualified # ♟️ E11.0 chess pawn +265F ; unqualified # ♟ E11.0 chess pawn +1F0CF ; fully-qualified # 🃏 E0.6 joker +1F004 ; fully-qualified # 🀄 E0.6 mahjong red dragon +1F3B4 ; fully-qualified # 🎴 E0.6 flower playing cards + +# subgroup: arts & crafts +1F3AD ; fully-qualified # 🎭 E0.6 performing arts +1F5BC FE0F ; fully-qualified # 🖼️ E0.7 framed picture +1F5BC ; unqualified # 🖼 E0.7 framed picture +1F3A8 ; fully-qualified # 🎨 E0.6 artist palette +1F9F5 ; fully-qualified # 🧵 E11.0 thread +1FAA1 ; fully-qualified # 🪡 E13.0 sewing needle +1F9F6 ; fully-qualified # 🧶 E11.0 yarn +1FAA2 ; fully-qualified # 🪢 E13.0 knot + +# Activities subtotal: 95 +# Activities subtotal: 95 w/o modifiers + +# group: Objects + +# subgroup: clothing +1F453 ; fully-qualified # 👓 E0.6 glasses +1F576 FE0F ; fully-qualified # 🕶️ E0.7 sunglasses +1F576 ; unqualified # 🕶 E0.7 sunglasses +1F97D ; fully-qualified # 🥽 E11.0 goggles +1F97C ; fully-qualified # 🥼 E11.0 lab coat +1F9BA ; fully-qualified # 🦺 E12.0 safety vest +1F454 ; fully-qualified # 👔 E0.6 necktie +1F455 ; fully-qualified # 👕 E0.6 t-shirt +1F456 ; fully-qualified # 👖 E0.6 jeans +1F9E3 ; fully-qualified # 🧣 E5.0 scarf +1F9E4 ; fully-qualified # 🧤 E5.0 gloves +1F9E5 ; fully-qualified # 🧥 E5.0 coat +1F9E6 ; fully-qualified # 🧦 E5.0 socks +1F457 ; fully-qualified # 👗 E0.6 dress +1F458 ; fully-qualified # 👘 E0.6 kimono +1F97B ; fully-qualified # 🥻 E12.0 sari +1FA71 ; fully-qualified # 🩱 E12.0 one-piece swimsuit +1FA72 ; fully-qualified # 🩲 E12.0 briefs +1FA73 ; fully-qualified # 🩳 E12.0 shorts +1F459 ; fully-qualified # 👙 E0.6 bikini +1F45A ; fully-qualified # 👚 E0.6 woman’s clothes +1F45B ; fully-qualified # 👛 E0.6 purse +1F45C ; fully-qualified # 👜 E0.6 handbag +1F45D ; fully-qualified # 👝 E0.6 clutch bag +1F6CD FE0F ; fully-qualified # 🛍️ E0.7 shopping bags +1F6CD ; unqualified # 🛍 E0.7 shopping bags +1F392 ; fully-qualified # 🎒 E0.6 backpack +1FA74 ; fully-qualified # 🩴 E13.0 thong sandal +1F45E ; fully-qualified # 👞 E0.6 man’s shoe +1F45F ; fully-qualified # 👟 E0.6 running shoe +1F97E ; fully-qualified # 🥾 E11.0 hiking boot +1F97F ; fully-qualified # 🥿 E11.0 flat shoe +1F460 ; fully-qualified # 👠 E0.6 high-heeled shoe +1F461 ; fully-qualified # 👡 E0.6 woman’s sandal +1FA70 ; fully-qualified # 🩰 E12.0 ballet shoes +1F462 ; fully-qualified # 👢 E0.6 woman’s boot +1F451 ; fully-qualified # 👑 E0.6 crown +1F452 ; fully-qualified # 👒 E0.6 woman’s hat +1F3A9 ; fully-qualified # 🎩 E0.6 top hat +1F393 ; fully-qualified # 🎓 E0.6 graduation cap +1F9E2 ; fully-qualified # 🧢 E5.0 billed cap +1FA96 ; fully-qualified # 🪖 E13.0 military helmet +26D1 FE0F ; fully-qualified # ⛑️ E0.7 rescue worker’s helmet +26D1 ; unqualified # ⛑ E0.7 rescue worker’s helmet +1F4FF ; fully-qualified # 📿 E1.0 prayer beads +1F484 ; fully-qualified # 💄 E0.6 lipstick +1F48D ; fully-qualified # 💍 E0.6 ring +1F48E ; fully-qualified # 💎 E0.6 gem stone + +# subgroup: sound +1F507 ; fully-qualified # 🔇 E1.0 muted speaker +1F508 ; fully-qualified # 🔈 E0.7 speaker low volume +1F509 ; fully-qualified # 🔉 E1.0 speaker medium volume +1F50A ; fully-qualified # 🔊 E0.6 speaker high volume +1F4E2 ; fully-qualified # 📢 E0.6 loudspeaker +1F4E3 ; fully-qualified # 📣 E0.6 megaphone +1F4EF ; fully-qualified # 📯 E1.0 postal horn +1F514 ; fully-qualified # 🔔 E0.6 bell +1F515 ; fully-qualified # 🔕 E1.0 bell with slash + +# subgroup: music +1F3BC ; fully-qualified # 🎼 E0.6 musical score +1F3B5 ; fully-qualified # 🎵 E0.6 musical note +1F3B6 ; fully-qualified # 🎶 E0.6 musical notes +1F399 FE0F ; fully-qualified # 🎙️ E0.7 studio microphone +1F399 ; unqualified # 🎙 E0.7 studio microphone +1F39A FE0F ; fully-qualified # 🎚️ E0.7 level slider +1F39A ; unqualified # 🎚 E0.7 level slider +1F39B FE0F ; fully-qualified # 🎛️ E0.7 control knobs +1F39B ; unqualified # 🎛 E0.7 control knobs +1F3A4 ; fully-qualified # 🎤 E0.6 microphone +1F3A7 ; fully-qualified # 🎧 E0.6 headphone +1F4FB ; fully-qualified # 📻 E0.6 radio + +# subgroup: musical-instrument +1F3B7 ; fully-qualified # 🎷 E0.6 saxophone +1FA97 ; fully-qualified # 🪗 E13.0 accordion +1F3B8 ; fully-qualified # 🎸 E0.6 guitar +1F3B9 ; fully-qualified # 🎹 E0.6 musical keyboard +1F3BA ; fully-qualified # 🎺 E0.6 trumpet +1F3BB ; fully-qualified # 🎻 E0.6 violin +1FA95 ; fully-qualified # 🪕 E12.0 banjo +1F941 ; fully-qualified # 🥁 E3.0 drum +1FA98 ; fully-qualified # 🪘 E13.0 long drum + +# subgroup: phone +1F4F1 ; fully-qualified # 📱 E0.6 mobile phone +1F4F2 ; fully-qualified # 📲 E0.6 mobile phone with arrow +260E FE0F ; fully-qualified # ☎️ E0.6 telephone +260E ; unqualified # ☎ E0.6 telephone +1F4DE ; fully-qualified # 📞 E0.6 telephone receiver +1F4DF ; fully-qualified # 📟 E0.6 pager +1F4E0 ; fully-qualified # 📠 E0.6 fax machine + +# subgroup: computer +1F50B ; fully-qualified # 🔋 E0.6 battery +1F50C ; fully-qualified # 🔌 E0.6 electric plug +1F4BB ; fully-qualified # 💻 E0.6 laptop +1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer +1F5A5 ; unqualified # 🖥 E0.7 desktop computer +1F5A8 FE0F ; fully-qualified # 🖨️ E0.7 printer +1F5A8 ; unqualified # 🖨 E0.7 printer +2328 FE0F ; fully-qualified # ⌨️ E1.0 keyboard +2328 ; unqualified # ⌨ E1.0 keyboard +1F5B1 FE0F ; fully-qualified # 🖱️ E0.7 computer mouse +1F5B1 ; unqualified # 🖱 E0.7 computer mouse +1F5B2 FE0F ; fully-qualified # 🖲️ E0.7 trackball +1F5B2 ; unqualified # 🖲 E0.7 trackball +1F4BD ; fully-qualified # 💽 E0.6 computer disk +1F4BE ; fully-qualified # 💾 E0.6 floppy disk +1F4BF ; fully-qualified # 💿 E0.6 optical disk +1F4C0 ; fully-qualified # 📀 E0.6 dvd +1F9EE ; fully-qualified # 🧮 E11.0 abacus + +# subgroup: light & video +1F3A5 ; fully-qualified # 🎥 E0.6 movie camera +1F39E FE0F ; fully-qualified # 🎞️ E0.7 film frames +1F39E ; unqualified # 🎞 E0.7 film frames +1F4FD FE0F ; fully-qualified # 📽️ E0.7 film projector +1F4FD ; unqualified # 📽 E0.7 film projector +1F3AC ; fully-qualified # 🎬 E0.6 clapper board +1F4FA ; fully-qualified # 📺 E0.6 television +1F4F7 ; fully-qualified # 📷 E0.6 camera +1F4F8 ; fully-qualified # 📸 E1.0 camera with flash +1F4F9 ; fully-qualified # 📹 E0.6 video camera +1F4FC ; fully-qualified # 📼 E0.6 videocassette +1F50D ; fully-qualified # 🔍 E0.6 magnifying glass tilted left +1F50E ; fully-qualified # 🔎 E0.6 magnifying glass tilted right +1F56F FE0F ; fully-qualified # 🕯️ E0.7 candle +1F56F ; unqualified # 🕯 E0.7 candle +1F4A1 ; fully-qualified # 💡 E0.6 light bulb +1F526 ; fully-qualified # 🔦 E0.6 flashlight +1F3EE ; fully-qualified # 🏮 E0.6 red paper lantern +1FA94 ; fully-qualified # 🪔 E12.0 diya lamp + +# subgroup: book-paper +1F4D4 ; fully-qualified # 📔 E0.6 notebook with decorative cover +1F4D5 ; fully-qualified # 📕 E0.6 closed book +1F4D6 ; fully-qualified # 📖 E0.6 open book +1F4D7 ; fully-qualified # 📗 E0.6 green book +1F4D8 ; fully-qualified # 📘 E0.6 blue book +1F4D9 ; fully-qualified # 📙 E0.6 orange book +1F4DA ; fully-qualified # 📚 E0.6 books +1F4D3 ; fully-qualified # 📓 E0.6 notebook +1F4D2 ; fully-qualified # 📒 E0.6 ledger +1F4C3 ; fully-qualified # 📃 E0.6 page with curl +1F4DC ; fully-qualified # 📜 E0.6 scroll +1F4C4 ; fully-qualified # 📄 E0.6 page facing up +1F4F0 ; fully-qualified # 📰 E0.6 newspaper +1F5DE FE0F ; fully-qualified # 🗞️ E0.7 rolled-up newspaper +1F5DE ; unqualified # 🗞 E0.7 rolled-up newspaper +1F4D1 ; fully-qualified # 📑 E0.6 bookmark tabs +1F516 ; fully-qualified # 🔖 E0.6 bookmark +1F3F7 FE0F ; fully-qualified # 🏷️ E0.7 label +1F3F7 ; unqualified # 🏷 E0.7 label + +# subgroup: money +1F4B0 ; fully-qualified # 💰 E0.6 money bag +1FA99 ; fully-qualified # 🪙 E13.0 coin +1F4B4 ; fully-qualified # 💴 E0.6 yen banknote +1F4B5 ; fully-qualified # 💵 E0.6 dollar banknote +1F4B6 ; fully-qualified # 💶 E1.0 euro banknote +1F4B7 ; fully-qualified # 💷 E1.0 pound banknote +1F4B8 ; fully-qualified # 💸 E0.6 money with wings +1F4B3 ; fully-qualified # 💳 E0.6 credit card +1F9FE ; fully-qualified # 🧾 E11.0 receipt +1F4B9 ; fully-qualified # 💹 E0.6 chart increasing with yen +1F4B1 ; fully-qualified # 💱 E0.6 currency exchange +1F4B2 ; fully-qualified # 💲 E0.6 heavy dollar sign + +# subgroup: mail +2709 FE0F ; fully-qualified # ✉️ E0.6 envelope +2709 ; unqualified # ✉ E0.6 envelope +1F4E7 ; fully-qualified # 📧 E0.6 e-mail +1F4E8 ; fully-qualified # 📨 E0.6 incoming envelope +1F4E9 ; fully-qualified # 📩 E0.6 envelope with arrow +1F4E4 ; fully-qualified # 📤 E0.6 outbox tray +1F4E5 ; fully-qualified # 📥 E0.6 inbox tray +1F4E6 ; fully-qualified # 📦 E0.6 package +1F4EB ; fully-qualified # 📫 E0.6 closed mailbox with raised flag +1F4EA ; fully-qualified # 📪 E0.6 closed mailbox with lowered flag +1F4EC ; fully-qualified # 📬 E0.7 open mailbox with raised flag +1F4ED ; fully-qualified # 📭 E0.7 open mailbox with lowered flag +1F4EE ; fully-qualified # 📮 E0.6 postbox +1F5F3 FE0F ; fully-qualified # 🗳️ E0.7 ballot box with ballot +1F5F3 ; unqualified # 🗳 E0.7 ballot box with ballot + +# subgroup: writing +270F FE0F ; fully-qualified # ✏️ E0.6 pencil +270F ; unqualified # ✏ E0.6 pencil +2712 FE0F ; fully-qualified # ✒️ E0.6 black nib +2712 ; unqualified # ✒ E0.6 black nib +1F58B FE0F ; fully-qualified # 🖋️ E0.7 fountain pen +1F58B ; unqualified # 🖋 E0.7 fountain pen +1F58A FE0F ; fully-qualified # 🖊️ E0.7 pen +1F58A ; unqualified # 🖊 E0.7 pen +1F58C FE0F ; fully-qualified # 🖌️ E0.7 paintbrush +1F58C ; unqualified # 🖌 E0.7 paintbrush +1F58D FE0F ; fully-qualified # 🖍️ E0.7 crayon +1F58D ; unqualified # 🖍 E0.7 crayon +1F4DD ; fully-qualified # 📝 E0.6 memo + +# subgroup: office +1F4BC ; fully-qualified # 💼 E0.6 briefcase +1F4C1 ; fully-qualified # 📁 E0.6 file folder +1F4C2 ; fully-qualified # 📂 E0.6 open file folder +1F5C2 FE0F ; fully-qualified # 🗂️ E0.7 card index dividers +1F5C2 ; unqualified # 🗂 E0.7 card index dividers +1F4C5 ; fully-qualified # 📅 E0.6 calendar +1F4C6 ; fully-qualified # 📆 E0.6 tear-off calendar +1F5D2 FE0F ; fully-qualified # 🗒️ E0.7 spiral notepad +1F5D2 ; unqualified # 🗒 E0.7 spiral notepad +1F5D3 FE0F ; fully-qualified # 🗓️ E0.7 spiral calendar +1F5D3 ; unqualified # 🗓 E0.7 spiral calendar +1F4C7 ; fully-qualified # 📇 E0.6 card index +1F4C8 ; fully-qualified # 📈 E0.6 chart increasing +1F4C9 ; fully-qualified # 📉 E0.6 chart decreasing +1F4CA ; fully-qualified # 📊 E0.6 bar chart +1F4CB ; fully-qualified # 📋 E0.6 clipboard +1F4CC ; fully-qualified # 📌 E0.6 pushpin +1F4CD ; fully-qualified # 📍 E0.6 round pushpin +1F4CE ; fully-qualified # 📎 E0.6 paperclip +1F587 FE0F ; fully-qualified # 🖇️ E0.7 linked paperclips +1F587 ; unqualified # 🖇 E0.7 linked paperclips +1F4CF ; fully-qualified # 📏 E0.6 straight ruler +1F4D0 ; fully-qualified # 📐 E0.6 triangular ruler +2702 FE0F ; fully-qualified # ✂️ E0.6 scissors +2702 ; unqualified # ✂ E0.6 scissors +1F5C3 FE0F ; fully-qualified # 🗃️ E0.7 card file box +1F5C3 ; unqualified # 🗃 E0.7 card file box +1F5C4 FE0F ; fully-qualified # 🗄️ E0.7 file cabinet +1F5C4 ; unqualified # 🗄 E0.7 file cabinet +1F5D1 FE0F ; fully-qualified # 🗑️ E0.7 wastebasket +1F5D1 ; unqualified # 🗑 E0.7 wastebasket + +# subgroup: lock +1F512 ; fully-qualified # 🔒 E0.6 locked +1F513 ; fully-qualified # 🔓 E0.6 unlocked +1F50F ; fully-qualified # 🔏 E0.6 locked with pen +1F510 ; fully-qualified # 🔐 E0.6 locked with key +1F511 ; fully-qualified # 🔑 E0.6 key +1F5DD FE0F ; fully-qualified # 🗝️ E0.7 old key +1F5DD ; unqualified # 🗝 E0.7 old key + +# subgroup: tool +1F528 ; fully-qualified # 🔨 E0.6 hammer +1FA93 ; fully-qualified # 🪓 E12.0 axe +26CF FE0F ; fully-qualified # ⛏️ E0.7 pick +26CF ; unqualified # ⛏ E0.7 pick +2692 FE0F ; fully-qualified # ⚒️ E1.0 hammer and pick +2692 ; unqualified # ⚒ E1.0 hammer and pick +1F6E0 FE0F ; fully-qualified # 🛠️ E0.7 hammer and wrench +1F6E0 ; unqualified # 🛠 E0.7 hammer and wrench +1F5E1 FE0F ; fully-qualified # 🗡️ E0.7 dagger +1F5E1 ; unqualified # 🗡 E0.7 dagger +2694 FE0F ; fully-qualified # ⚔️ E1.0 crossed swords +2694 ; unqualified # ⚔ E1.0 crossed swords +1F52B ; fully-qualified # 🔫 E0.6 pistol +1FA83 ; fully-qualified # 🪃 E13.0 boomerang +1F3F9 ; fully-qualified # 🏹 E1.0 bow and arrow +1F6E1 FE0F ; fully-qualified # 🛡️ E0.7 shield +1F6E1 ; unqualified # 🛡 E0.7 shield +1FA9A ; fully-qualified # 🪚 E13.0 carpentry saw +1F527 ; fully-qualified # 🔧 E0.6 wrench +1FA9B ; fully-qualified # 🪛 E13.0 screwdriver +1F529 ; fully-qualified # 🔩 E0.6 nut and bolt +2699 FE0F ; fully-qualified # ⚙️ E1.0 gear +2699 ; unqualified # ⚙ E1.0 gear +1F5DC FE0F ; fully-qualified # 🗜️ E0.7 clamp +1F5DC ; unqualified # 🗜 E0.7 clamp +2696 FE0F ; fully-qualified # ⚖️ E1.0 balance scale +2696 ; unqualified # ⚖ E1.0 balance scale +1F9AF ; fully-qualified # 🦯 E12.0 probing cane +1F517 ; fully-qualified # 🔗 E0.6 link +26D3 FE0F ; fully-qualified # ⛓️ E0.7 chains +26D3 ; unqualified # ⛓ E0.7 chains +1FA9D ; fully-qualified # 🪝 E13.0 hook +1F9F0 ; fully-qualified # 🧰 E11.0 toolbox +1F9F2 ; fully-qualified # 🧲 E11.0 magnet +1FA9C ; fully-qualified # 🪜 E13.0 ladder + +# subgroup: science +2697 FE0F ; fully-qualified # ⚗️ E1.0 alembic +2697 ; unqualified # ⚗ E1.0 alembic +1F9EA ; fully-qualified # 🧪 E11.0 test tube +1F9EB ; fully-qualified # 🧫 E11.0 petri dish +1F9EC ; fully-qualified # 🧬 E11.0 dna +1F52C ; fully-qualified # 🔬 E1.0 microscope +1F52D ; fully-qualified # 🔭 E1.0 telescope +1F4E1 ; fully-qualified # 📡 E0.6 satellite antenna + +# subgroup: medical +1F489 ; fully-qualified # 💉 E0.6 syringe +1FA78 ; fully-qualified # 🩸 E12.0 drop of blood +1F48A ; fully-qualified # 💊 E0.6 pill +1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage +1FA7A ; fully-qualified # 🩺 E12.0 stethoscope + +# subgroup: household +1F6AA ; fully-qualified # 🚪 E0.6 door +1F6D7 ; fully-qualified # 🛗 E13.0 elevator +1FA9E ; fully-qualified # 🪞 E13.0 mirror +1FA9F ; fully-qualified # 🪟 E13.0 window +1F6CF FE0F ; fully-qualified # 🛏️ E0.7 bed +1F6CF ; unqualified # 🛏 E0.7 bed +1F6CB FE0F ; fully-qualified # 🛋️ E0.7 couch and lamp +1F6CB ; unqualified # 🛋 E0.7 couch and lamp +1FA91 ; fully-qualified # 🪑 E12.0 chair +1F6BD ; fully-qualified # 🚽 E0.6 toilet +1FAA0 ; fully-qualified # 🪠 E13.0 plunger +1F6BF ; fully-qualified # 🚿 E1.0 shower +1F6C1 ; fully-qualified # 🛁 E1.0 bathtub +1FAA4 ; fully-qualified # 🪤 E13.0 mouse trap +1FA92 ; fully-qualified # 🪒 E12.0 razor +1F9F4 ; fully-qualified # 🧴 E11.0 lotion bottle +1F9F7 ; fully-qualified # 🧷 E11.0 safety pin +1F9F9 ; fully-qualified # 🧹 E11.0 broom +1F9FA ; fully-qualified # 🧺 E11.0 basket +1F9FB ; fully-qualified # 🧻 E11.0 roll of paper +1FAA3 ; fully-qualified # 🪣 E13.0 bucket +1F9FC ; fully-qualified # 🧼 E11.0 soap +1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush +1F9FD ; fully-qualified # 🧽 E11.0 sponge +1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher +1F6D2 ; fully-qualified # 🛒 E3.0 shopping cart + +# subgroup: other-object +1F6AC ; fully-qualified # 🚬 E0.6 cigarette +26B0 FE0F ; fully-qualified # ⚰️ E1.0 coffin +26B0 ; unqualified # ⚰ E1.0 coffin +1FAA6 ; fully-qualified # 🪦 E13.0 headstone +26B1 FE0F ; fully-qualified # ⚱️ E1.0 funeral urn +26B1 ; unqualified # ⚱ E1.0 funeral urn +1F5FF ; fully-qualified # 🗿 E0.6 moai +1FAA7 ; fully-qualified # 🪧 E13.0 placard + +# Objects subtotal: 301 +# Objects subtotal: 301 w/o modifiers + +# group: Symbols + +# subgroup: transport-sign +1F3E7 ; fully-qualified # 🏧 E0.6 ATM sign +1F6AE ; fully-qualified # 🚮 E1.0 litter in bin sign +1F6B0 ; fully-qualified # 🚰 E1.0 potable water +267F ; fully-qualified # ♿ E0.6 wheelchair symbol +1F6B9 ; fully-qualified # 🚹 E0.6 men’s room +1F6BA ; fully-qualified # 🚺 E0.6 women’s room +1F6BB ; fully-qualified # 🚻 E0.6 restroom +1F6BC ; fully-qualified # 🚼 E0.6 baby symbol +1F6BE ; fully-qualified # 🚾 E0.6 water closet +1F6C2 ; fully-qualified # 🛂 E1.0 passport control +1F6C3 ; fully-qualified # 🛃 E1.0 customs +1F6C4 ; fully-qualified # 🛄 E1.0 baggage claim +1F6C5 ; fully-qualified # 🛅 E1.0 left luggage + +# subgroup: warning +26A0 FE0F ; fully-qualified # ⚠️ E0.6 warning +26A0 ; unqualified # ⚠ E0.6 warning +1F6B8 ; fully-qualified # 🚸 E1.0 children crossing +26D4 ; fully-qualified # ⛔ E0.6 no entry +1F6AB ; fully-qualified # 🚫 E0.6 prohibited +1F6B3 ; fully-qualified # 🚳 E1.0 no bicycles +1F6AD ; fully-qualified # 🚭 E0.6 no smoking +1F6AF ; fully-qualified # 🚯 E1.0 no littering +1F6B1 ; fully-qualified # 🚱 E1.0 non-potable water +1F6B7 ; fully-qualified # 🚷 E1.0 no pedestrians +1F4F5 ; fully-qualified # 📵 E1.0 no mobile phones +1F51E ; fully-qualified # 🔞 E0.6 no one under eighteen +2622 FE0F ; fully-qualified # ☢️ E1.0 radioactive +2622 ; unqualified # ☢ E1.0 radioactive +2623 FE0F ; fully-qualified # ☣️ E1.0 biohazard +2623 ; unqualified # ☣ E1.0 biohazard + +# subgroup: arrow +2B06 FE0F ; fully-qualified # ⬆️ E0.6 up arrow +2B06 ; unqualified # ⬆ E0.6 up arrow +2197 FE0F ; fully-qualified # ↗️ E0.6 up-right arrow +2197 ; unqualified # ↗ E0.6 up-right arrow +27A1 FE0F ; fully-qualified # ➡️ E0.6 right arrow +27A1 ; unqualified # ➡ E0.6 right arrow +2198 FE0F ; fully-qualified # ↘️ E0.6 down-right arrow +2198 ; unqualified # ↘ E0.6 down-right arrow +2B07 FE0F ; fully-qualified # ⬇️ E0.6 down arrow +2B07 ; unqualified # ⬇ E0.6 down arrow +2199 FE0F ; fully-qualified # ↙️ E0.6 down-left arrow +2199 ; unqualified # ↙ E0.6 down-left arrow +2B05 FE0F ; fully-qualified # ⬅️ E0.6 left arrow +2B05 ; unqualified # ⬅ E0.6 left arrow +2196 FE0F ; fully-qualified # ↖️ E0.6 up-left arrow +2196 ; unqualified # ↖ E0.6 up-left arrow +2195 FE0F ; fully-qualified # ↕️ E0.6 up-down arrow +2195 ; unqualified # ↕ E0.6 up-down arrow +2194 FE0F ; fully-qualified # ↔️ E0.6 left-right arrow +2194 ; unqualified # ↔ E0.6 left-right arrow +21A9 FE0F ; fully-qualified # ↩️ E0.6 right arrow curving left +21A9 ; unqualified # ↩ E0.6 right arrow curving left +21AA FE0F ; fully-qualified # ↪️ E0.6 left arrow curving right +21AA ; unqualified # ↪ E0.6 left arrow curving right +2934 FE0F ; fully-qualified # ⤴️ E0.6 right arrow curving up +2934 ; unqualified # ⤴ E0.6 right arrow curving up +2935 FE0F ; fully-qualified # ⤵️ E0.6 right arrow curving down +2935 ; unqualified # ⤵ E0.6 right arrow curving down +1F503 ; fully-qualified # 🔃 E0.6 clockwise vertical arrows +1F504 ; fully-qualified # 🔄 E1.0 counterclockwise arrows button +1F519 ; fully-qualified # 🔙 E0.6 BACK arrow +1F51A ; fully-qualified # 🔚 E0.6 END arrow +1F51B ; fully-qualified # 🔛 E0.6 ON! arrow +1F51C ; fully-qualified # 🔜 E0.6 SOON arrow +1F51D ; fully-qualified # 🔝 E0.6 TOP arrow + +# subgroup: religion +1F6D0 ; fully-qualified # 🛐 E1.0 place of worship +269B FE0F ; fully-qualified # ⚛️ E1.0 atom symbol +269B ; unqualified # ⚛ E1.0 atom symbol +1F549 FE0F ; fully-qualified # 🕉️ E0.7 om +1F549 ; unqualified # 🕉 E0.7 om +2721 FE0F ; fully-qualified # ✡️ E0.7 star of David +2721 ; unqualified # ✡ E0.7 star of David +2638 FE0F ; fully-qualified # ☸️ E0.7 wheel of dharma +2638 ; unqualified # ☸ E0.7 wheel of dharma +262F FE0F ; fully-qualified # ☯️ E0.7 yin yang +262F ; unqualified # ☯ E0.7 yin yang +271D FE0F ; fully-qualified # ✝️ E0.7 latin cross +271D ; unqualified # ✝ E0.7 latin cross +2626 FE0F ; fully-qualified # ☦️ E1.0 orthodox cross +2626 ; unqualified # ☦ E1.0 orthodox cross +262A FE0F ; fully-qualified # ☪️ E0.7 star and crescent +262A ; unqualified # ☪ E0.7 star and crescent +262E FE0F ; fully-qualified # ☮️ E1.0 peace symbol +262E ; unqualified # ☮ E1.0 peace symbol +1F54E ; fully-qualified # 🕎 E1.0 menorah +1F52F ; fully-qualified # 🔯 E0.6 dotted six-pointed star + +# subgroup: zodiac +2648 ; fully-qualified # ♈ E0.6 Aries +2649 ; fully-qualified # ♉ E0.6 Taurus +264A ; fully-qualified # ♊ E0.6 Gemini +264B ; fully-qualified # ♋ E0.6 Cancer +264C ; fully-qualified # ♌ E0.6 Leo +264D ; fully-qualified # ♍ E0.6 Virgo +264E ; fully-qualified # ♎ E0.6 Libra +264F ; fully-qualified # ♏ E0.6 Scorpio +2650 ; fully-qualified # ♐ E0.6 Sagittarius +2651 ; fully-qualified # ♑ E0.6 Capricorn +2652 ; fully-qualified # ♒ E0.6 Aquarius +2653 ; fully-qualified # ♓ E0.6 Pisces +26CE ; fully-qualified # ⛎ E0.6 Ophiuchus + +# subgroup: av-symbol +1F500 ; fully-qualified # 🔀 E1.0 shuffle tracks button +1F501 ; fully-qualified # 🔁 E1.0 repeat button +1F502 ; fully-qualified # 🔂 E1.0 repeat single button +25B6 FE0F ; fully-qualified # ▶️ E0.6 play button +25B6 ; unqualified # ▶ E0.6 play button +23E9 ; fully-qualified # ⏩ E0.6 fast-forward button +23ED FE0F ; fully-qualified # ⏭️ E0.7 next track button +23ED ; unqualified # ⏭ E0.7 next track button +23EF FE0F ; fully-qualified # ⏯️ E1.0 play or pause button +23EF ; unqualified # ⏯ E1.0 play or pause button +25C0 FE0F ; fully-qualified # ◀️ E0.6 reverse button +25C0 ; unqualified # ◀ E0.6 reverse button +23EA ; fully-qualified # ⏪ E0.6 fast reverse button +23EE FE0F ; fully-qualified # ⏮️ E0.7 last track button +23EE ; unqualified # ⏮ E0.7 last track button +1F53C ; fully-qualified # 🔼 E0.6 upwards button +23EB ; fully-qualified # ⏫ E0.6 fast up button +1F53D ; fully-qualified # 🔽 E0.6 downwards button +23EC ; fully-qualified # ⏬ E0.6 fast down button +23F8 FE0F ; fully-qualified # ⏸️ E0.7 pause button +23F8 ; unqualified # ⏸ E0.7 pause button +23F9 FE0F ; fully-qualified # ⏹️ E0.7 stop button +23F9 ; unqualified # ⏹ E0.7 stop button +23FA FE0F ; fully-qualified # ⏺️ E0.7 record button +23FA ; unqualified # ⏺ E0.7 record button +23CF FE0F ; fully-qualified # ⏏️ E1.0 eject button +23CF ; unqualified # ⏏ E1.0 eject button +1F3A6 ; fully-qualified # 🎦 E0.6 cinema +1F505 ; fully-qualified # 🔅 E1.0 dim button +1F506 ; fully-qualified # 🔆 E1.0 bright button +1F4F6 ; fully-qualified # 📶 E0.6 antenna bars +1F4F3 ; fully-qualified # 📳 E0.6 vibration mode +1F4F4 ; fully-qualified # 📴 E0.6 mobile phone off + +# subgroup: gender +2640 FE0F ; fully-qualified # ♀️ E4.0 female sign +2640 ; unqualified # ♀ E4.0 female sign +2642 FE0F ; fully-qualified # ♂️ E4.0 male sign +2642 ; unqualified # ♂ E4.0 male sign +26A7 FE0F ; fully-qualified # ⚧️ E13.0 transgender symbol +26A7 ; unqualified # ⚧ E13.0 transgender symbol + +# subgroup: other-symbol +2695 FE0F ; fully-qualified # ⚕️ E4.0 medical symbol +2695 ; unqualified # ⚕ E4.0 medical symbol +267E FE0F ; fully-qualified # ♾️ E11.0 infinity +267E ; unqualified # ♾ E11.0 infinity +267B FE0F ; fully-qualified # ♻️ E0.6 recycling symbol +267B ; unqualified # ♻ E0.6 recycling symbol +269C FE0F ; fully-qualified # ⚜️ E1.0 fleur-de-lis +269C ; unqualified # ⚜ E1.0 fleur-de-lis +1F531 ; fully-qualified # 🔱 E0.6 trident emblem +1F4DB ; fully-qualified # 📛 E0.6 name badge +1F530 ; fully-qualified # 🔰 E0.6 Japanese symbol for beginner +2B55 ; fully-qualified # ⭕ E0.6 hollow red circle +2705 ; fully-qualified # ✅ E0.6 check mark button +2611 FE0F ; fully-qualified # ☑️ E0.6 check box with check +2611 ; unqualified # ☑ E0.6 check box with check +2714 FE0F ; fully-qualified # ✔️ E0.6 check mark +2714 ; unqualified # ✔ E0.6 check mark +2716 FE0F ; fully-qualified # ✖️ E0.6 multiplication sign +2716 ; unqualified # ✖ E0.6 multiplication sign +274C ; fully-qualified # ❌ E0.6 cross mark +274E ; fully-qualified # ❎ E0.6 cross mark button +2795 ; fully-qualified # ➕ E0.6 plus sign +2796 ; fully-qualified # ➖ E0.6 minus sign +2797 ; fully-qualified # ➗ E0.6 division sign +27B0 ; fully-qualified # ➰ E0.6 curly loop +27BF ; fully-qualified # ➿ E1.0 double curly loop +303D FE0F ; fully-qualified # 〽️ E0.6 part alternation mark +303D ; unqualified # 〽 E0.6 part alternation mark +2733 FE0F ; fully-qualified # ✳️ E0.6 eight-spoked asterisk +2733 ; unqualified # ✳ E0.6 eight-spoked asterisk +2734 FE0F ; fully-qualified # ✴️ E0.6 eight-pointed star +2734 ; unqualified # ✴ E0.6 eight-pointed star +2747 FE0F ; fully-qualified # ❇️ E0.6 sparkle +2747 ; unqualified # ❇ E0.6 sparkle +203C FE0F ; fully-qualified # ‼️ E0.6 double exclamation mark +203C ; unqualified # ‼ E0.6 double exclamation mark +2049 FE0F ; fully-qualified # ⁉️ E0.6 exclamation question mark +2049 ; unqualified # ⁉ E0.6 exclamation question mark +2753 ; fully-qualified # ❓ E0.6 question mark +2754 ; fully-qualified # ❔ E0.6 white question mark +2755 ; fully-qualified # ❕ E0.6 white exclamation mark +2757 ; fully-qualified # ❗ E0.6 exclamation mark +3030 FE0F ; fully-qualified # 〰️ E0.6 wavy dash +3030 ; unqualified # 〰 E0.6 wavy dash +00A9 FE0F ; fully-qualified # ©️ E0.6 copyright +00A9 ; unqualified # © E0.6 copyright +00AE FE0F ; fully-qualified # ®️ E0.6 registered +00AE ; unqualified # ® E0.6 registered +2122 FE0F ; fully-qualified # ™️ E0.6 trade mark +2122 ; unqualified # ™ E0.6 trade mark + +# subgroup: keycap +0023 FE0F 20E3 ; fully-qualified # #️⃣ E0.6 keycap: # +0023 20E3 ; unqualified # #⃣ E0.6 keycap: # +002A FE0F 20E3 ; fully-qualified # *️⃣ E2.0 keycap: * +002A 20E3 ; unqualified # *⃣ E2.0 keycap: * +0030 FE0F 20E3 ; fully-qualified # 0️⃣ E0.6 keycap: 0 +0030 20E3 ; unqualified # 0⃣ E0.6 keycap: 0 +0031 FE0F 20E3 ; fully-qualified # 1️⃣ E0.6 keycap: 1 +0031 20E3 ; unqualified # 1⃣ E0.6 keycap: 1 +0032 FE0F 20E3 ; fully-qualified # 2️⃣ E0.6 keycap: 2 +0032 20E3 ; unqualified # 2⃣ E0.6 keycap: 2 +0033 FE0F 20E3 ; fully-qualified # 3️⃣ E0.6 keycap: 3 +0033 20E3 ; unqualified # 3⃣ E0.6 keycap: 3 +0034 FE0F 20E3 ; fully-qualified # 4️⃣ E0.6 keycap: 4 +0034 20E3 ; unqualified # 4⃣ E0.6 keycap: 4 +0035 FE0F 20E3 ; fully-qualified # 5️⃣ E0.6 keycap: 5 +0035 20E3 ; unqualified # 5⃣ E0.6 keycap: 5 +0036 FE0F 20E3 ; fully-qualified # 6️⃣ E0.6 keycap: 6 +0036 20E3 ; unqualified # 6⃣ E0.6 keycap: 6 +0037 FE0F 20E3 ; fully-qualified # 7️⃣ E0.6 keycap: 7 +0037 20E3 ; unqualified # 7⃣ E0.6 keycap: 7 +0038 FE0F 20E3 ; fully-qualified # 8️⃣ E0.6 keycap: 8 +0038 20E3 ; unqualified # 8⃣ E0.6 keycap: 8 +0039 FE0F 20E3 ; fully-qualified # 9️⃣ E0.6 keycap: 9 +0039 20E3 ; unqualified # 9⃣ E0.6 keycap: 9 +1F51F ; fully-qualified # 🔟 E0.6 keycap: 10 + +# subgroup: alphanum +1F520 ; fully-qualified # 🔠 E0.6 input latin uppercase +1F521 ; fully-qualified # 🔡 E0.6 input latin lowercase +1F522 ; fully-qualified # 🔢 E0.6 input numbers +1F523 ; fully-qualified # 🔣 E0.6 input symbols +1F524 ; fully-qualified # 🔤 E0.6 input latin letters +1F170 FE0F ; fully-qualified # 🅰️ E0.6 A button (blood type) +1F170 ; unqualified # 🅰 E0.6 A button (blood type) +1F18E ; fully-qualified # 🆎 E0.6 AB button (blood type) +1F171 FE0F ; fully-qualified # 🅱️ E0.6 B button (blood type) +1F171 ; unqualified # 🅱 E0.6 B button (blood type) +1F191 ; fully-qualified # 🆑 E0.6 CL button +1F192 ; fully-qualified # 🆒 E0.6 COOL button +1F193 ; fully-qualified # 🆓 E0.6 FREE button +2139 FE0F ; fully-qualified # ℹ️ E0.6 information +2139 ; unqualified # ℹ E0.6 information +1F194 ; fully-qualified # 🆔 E0.6 ID button +24C2 FE0F ; fully-qualified # Ⓜ️ E0.6 circled M +24C2 ; unqualified # Ⓜ E0.6 circled M +1F195 ; fully-qualified # 🆕 E0.6 NEW button +1F196 ; fully-qualified # 🆖 E0.6 NG button +1F17E FE0F ; fully-qualified # 🅾️ E0.6 O button (blood type) +1F17E ; unqualified # 🅾 E0.6 O button (blood type) +1F197 ; fully-qualified # 🆗 E0.6 OK button +1F17F FE0F ; fully-qualified # 🅿️ E0.6 P button +1F17F ; unqualified # 🅿 E0.6 P button +1F198 ; fully-qualified # 🆘 E0.6 SOS button +1F199 ; fully-qualified # 🆙 E0.6 UP! button +1F19A ; fully-qualified # 🆚 E0.6 VS button +1F201 ; fully-qualified # 🈁 E0.6 Japanese “here” button +1F202 FE0F ; fully-qualified # 🈂️ E0.6 Japanese “service charge” button +1F202 ; unqualified # 🈂 E0.6 Japanese “service charge” button +1F237 FE0F ; fully-qualified # 🈷️ E0.6 Japanese “monthly amount” button +1F237 ; unqualified # 🈷 E0.6 Japanese “monthly amount” button +1F236 ; fully-qualified # 🈶 E0.6 Japanese “not free of charge” button +1F22F ; fully-qualified # 🈯 E0.6 Japanese “reserved” button +1F250 ; fully-qualified # 🉐 E0.6 Japanese “bargain” button +1F239 ; fully-qualified # 🈹 E0.6 Japanese “discount” button +1F21A ; fully-qualified # 🈚 E0.6 Japanese “free of charge” button +1F232 ; fully-qualified # 🈲 E0.6 Japanese “prohibited” button +1F251 ; fully-qualified # 🉑 E0.6 Japanese “acceptable” button +1F238 ; fully-qualified # 🈸 E0.6 Japanese “application” button +1F234 ; fully-qualified # 🈴 E0.6 Japanese “passing grade” button +1F233 ; fully-qualified # 🈳 E0.6 Japanese “vacancy” button +3297 FE0F ; fully-qualified # ㊗️ E0.6 Japanese “congratulations” button +3297 ; unqualified # ㊗ E0.6 Japanese “congratulations” button +3299 FE0F ; fully-qualified # ㊙️ E0.6 Japanese “secret” button +3299 ; unqualified # ㊙ E0.6 Japanese “secret” button +1F23A ; fully-qualified # 🈺 E0.6 Japanese “open for business” button +1F235 ; fully-qualified # 🈵 E0.6 Japanese “no vacancy” button + +# subgroup: geometric +1F534 ; fully-qualified # 🔴 E0.6 red circle +1F7E0 ; fully-qualified # 🟠 E12.0 orange circle +1F7E1 ; fully-qualified # 🟡 E12.0 yellow circle +1F7E2 ; fully-qualified # 🟢 E12.0 green circle +1F535 ; fully-qualified # 🔵 E0.6 blue circle +1F7E3 ; fully-qualified # 🟣 E12.0 purple circle +1F7E4 ; fully-qualified # 🟤 E12.0 brown circle +26AB ; fully-qualified # ⚫ E0.6 black circle +26AA ; fully-qualified # ⚪ E0.6 white circle +1F7E5 ; fully-qualified # 🟥 E12.0 red square +1F7E7 ; fully-qualified # 🟧 E12.0 orange square +1F7E8 ; fully-qualified # 🟨 E12.0 yellow square +1F7E9 ; fully-qualified # 🟩 E12.0 green square +1F7E6 ; fully-qualified # 🟦 E12.0 blue square +1F7EA ; fully-qualified # 🟪 E12.0 purple square +1F7EB ; fully-qualified # 🟫 E12.0 brown square +2B1B ; fully-qualified # ⬛ E0.6 black large square +2B1C ; fully-qualified # ⬜ E0.6 white large square +25FC FE0F ; fully-qualified # ◼️ E0.6 black medium square +25FC ; unqualified # ◼ E0.6 black medium square +25FB FE0F ; fully-qualified # ◻️ E0.6 white medium square +25FB ; unqualified # ◻ E0.6 white medium square +25FE ; fully-qualified # ◾ E0.6 black medium-small square +25FD ; fully-qualified # ◽ E0.6 white medium-small square +25AA FE0F ; fully-qualified # ▪️ E0.6 black small square +25AA ; unqualified # ▪ E0.6 black small square +25AB FE0F ; fully-qualified # ▫️ E0.6 white small square +25AB ; unqualified # ▫ E0.6 white small square +1F536 ; fully-qualified # 🔶 E0.6 large orange diamond +1F537 ; fully-qualified # 🔷 E0.6 large blue diamond +1F538 ; fully-qualified # 🔸 E0.6 small orange diamond +1F539 ; fully-qualified # 🔹 E0.6 small blue diamond +1F53A ; fully-qualified # 🔺 E0.6 red triangle pointed up +1F53B ; fully-qualified # 🔻 E0.6 red triangle pointed down +1F4A0 ; fully-qualified # 💠 E0.6 diamond with a dot +1F518 ; fully-qualified # 🔘 E0.6 radio button +1F533 ; fully-qualified # 🔳 E0.6 white square button +1F532 ; fully-qualified # 🔲 E0.6 black square button + +# Symbols subtotal: 299 +# Symbols subtotal: 299 w/o modifiers + +# group: Flags + +# subgroup: flag +1F3C1 ; fully-qualified # 🏁 E0.6 chequered flag +1F6A9 ; fully-qualified # 🚩 E0.6 triangular flag +1F38C ; fully-qualified # 🎌 E0.6 crossed flags +1F3F4 ; fully-qualified # 🏴 E1.0 black flag +1F3F3 FE0F ; fully-qualified # 🏳️ E0.7 white flag +1F3F3 ; unqualified # 🏳 E0.7 white flag +1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 E4.0 rainbow flag +1F3F3 200D 1F308 ; unqualified # 🏳‍🌈 E4.0 rainbow flag +1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # 🏳️‍⚧️ E13.0 transgender flag +1F3F3 200D 26A7 FE0F ; unqualified # 🏳‍⚧️ E13.0 transgender flag +1F3F3 FE0F 200D 26A7 ; unqualified # 🏳️‍⚧ E13.0 transgender flag +1F3F3 200D 26A7 ; unqualified # 🏳‍⚧ E13.0 transgender flag +1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ E11.0 pirate flag +1F3F4 200D 2620 ; minimally-qualified # 🏴‍☠ E11.0 pirate flag + +# subgroup: country-flag +1F1E6 1F1E8 ; fully-qualified # 🇦🇨 E2.0 flag: Ascension Island +1F1E6 1F1E9 ; fully-qualified # 🇦🇩 E2.0 flag: Andorra +1F1E6 1F1EA ; fully-qualified # 🇦🇪 E2.0 flag: United Arab Emirates +1F1E6 1F1EB ; fully-qualified # 🇦🇫 E2.0 flag: Afghanistan +1F1E6 1F1EC ; fully-qualified # 🇦🇬 E2.0 flag: Antigua & Barbuda +1F1E6 1F1EE ; fully-qualified # 🇦🇮 E2.0 flag: Anguilla +1F1E6 1F1F1 ; fully-qualified # 🇦🇱 E2.0 flag: Albania +1F1E6 1F1F2 ; fully-qualified # 🇦🇲 E2.0 flag: Armenia +1F1E6 1F1F4 ; fully-qualified # 🇦🇴 E2.0 flag: Angola +1F1E6 1F1F6 ; fully-qualified # 🇦🇶 E2.0 flag: Antarctica +1F1E6 1F1F7 ; fully-qualified # 🇦🇷 E2.0 flag: Argentina +1F1E6 1F1F8 ; fully-qualified # 🇦🇸 E2.0 flag: American Samoa +1F1E6 1F1F9 ; fully-qualified # 🇦🇹 E2.0 flag: Austria +1F1E6 1F1FA ; fully-qualified # 🇦🇺 E2.0 flag: Australia +1F1E6 1F1FC ; fully-qualified # 🇦🇼 E2.0 flag: Aruba +1F1E6 1F1FD ; fully-qualified # 🇦🇽 E2.0 flag: Åland Islands +1F1E6 1F1FF ; fully-qualified # 🇦🇿 E2.0 flag: Azerbaijan +1F1E7 1F1E6 ; fully-qualified # 🇧🇦 E2.0 flag: Bosnia & Herzegovina +1F1E7 1F1E7 ; fully-qualified # 🇧🇧 E2.0 flag: Barbados +1F1E7 1F1E9 ; fully-qualified # 🇧🇩 E2.0 flag: Bangladesh +1F1E7 1F1EA ; fully-qualified # 🇧🇪 E2.0 flag: Belgium +1F1E7 1F1EB ; fully-qualified # 🇧🇫 E2.0 flag: Burkina Faso +1F1E7 1F1EC ; fully-qualified # 🇧🇬 E2.0 flag: Bulgaria +1F1E7 1F1ED ; fully-qualified # 🇧🇭 E2.0 flag: Bahrain +1F1E7 1F1EE ; fully-qualified # 🇧🇮 E2.0 flag: Burundi +1F1E7 1F1EF ; fully-qualified # 🇧🇯 E2.0 flag: Benin +1F1E7 1F1F1 ; fully-qualified # 🇧🇱 E2.0 flag: St. Barthélemy +1F1E7 1F1F2 ; fully-qualified # 🇧🇲 E2.0 flag: Bermuda +1F1E7 1F1F3 ; fully-qualified # 🇧🇳 E2.0 flag: Brunei +1F1E7 1F1F4 ; fully-qualified # 🇧🇴 E2.0 flag: Bolivia +1F1E7 1F1F6 ; fully-qualified # 🇧🇶 E2.0 flag: Caribbean Netherlands +1F1E7 1F1F7 ; fully-qualified # 🇧🇷 E2.0 flag: Brazil +1F1E7 1F1F8 ; fully-qualified # 🇧🇸 E2.0 flag: Bahamas +1F1E7 1F1F9 ; fully-qualified # 🇧🇹 E2.0 flag: Bhutan +1F1E7 1F1FB ; fully-qualified # 🇧🇻 E2.0 flag: Bouvet Island +1F1E7 1F1FC ; fully-qualified # 🇧🇼 E2.0 flag: Botswana +1F1E7 1F1FE ; fully-qualified # 🇧🇾 E2.0 flag: Belarus +1F1E7 1F1FF ; fully-qualified # 🇧🇿 E2.0 flag: Belize +1F1E8 1F1E6 ; fully-qualified # 🇨🇦 E2.0 flag: Canada +1F1E8 1F1E8 ; fully-qualified # 🇨🇨 E2.0 flag: Cocos (Keeling) Islands +1F1E8 1F1E9 ; fully-qualified # 🇨🇩 E2.0 flag: Congo - Kinshasa +1F1E8 1F1EB ; fully-qualified # 🇨🇫 E2.0 flag: Central African Republic +1F1E8 1F1EC ; fully-qualified # 🇨🇬 E2.0 flag: Congo - Brazzaville +1F1E8 1F1ED ; fully-qualified # 🇨🇭 E2.0 flag: Switzerland +1F1E8 1F1EE ; fully-qualified # 🇨🇮 E2.0 flag: Côte d’Ivoire +1F1E8 1F1F0 ; fully-qualified # 🇨🇰 E2.0 flag: Cook Islands +1F1E8 1F1F1 ; fully-qualified # 🇨🇱 E2.0 flag: Chile +1F1E8 1F1F2 ; fully-qualified # 🇨🇲 E2.0 flag: Cameroon +1F1E8 1F1F3 ; fully-qualified # 🇨🇳 E0.6 flag: China +1F1E8 1F1F4 ; fully-qualified # 🇨🇴 E2.0 flag: Colombia +1F1E8 1F1F5 ; fully-qualified # 🇨🇵 E2.0 flag: Clipperton Island +1F1E8 1F1F7 ; fully-qualified # 🇨🇷 E2.0 flag: Costa Rica +1F1E8 1F1FA ; fully-qualified # 🇨🇺 E2.0 flag: Cuba +1F1E8 1F1FB ; fully-qualified # 🇨🇻 E2.0 flag: Cape Verde +1F1E8 1F1FC ; fully-qualified # 🇨🇼 E2.0 flag: Curaçao +1F1E8 1F1FD ; fully-qualified # 🇨🇽 E2.0 flag: Christmas Island +1F1E8 1F1FE ; fully-qualified # 🇨🇾 E2.0 flag: Cyprus +1F1E8 1F1FF ; fully-qualified # 🇨🇿 E2.0 flag: Czechia +1F1E9 1F1EA ; fully-qualified # 🇩🇪 E0.6 flag: Germany +1F1E9 1F1EC ; fully-qualified # 🇩🇬 E2.0 flag: Diego Garcia +1F1E9 1F1EF ; fully-qualified # 🇩🇯 E2.0 flag: Djibouti +1F1E9 1F1F0 ; fully-qualified # 🇩🇰 E2.0 flag: Denmark +1F1E9 1F1F2 ; fully-qualified # 🇩🇲 E2.0 flag: Dominica +1F1E9 1F1F4 ; fully-qualified # 🇩🇴 E2.0 flag: Dominican Republic +1F1E9 1F1FF ; fully-qualified # 🇩🇿 E2.0 flag: Algeria +1F1EA 1F1E6 ; fully-qualified # 🇪🇦 E2.0 flag: Ceuta & Melilla +1F1EA 1F1E8 ; fully-qualified # 🇪🇨 E2.0 flag: Ecuador +1F1EA 1F1EA ; fully-qualified # 🇪🇪 E2.0 flag: Estonia +1F1EA 1F1EC ; fully-qualified # 🇪🇬 E2.0 flag: Egypt +1F1EA 1F1ED ; fully-qualified # 🇪🇭 E2.0 flag: Western Sahara +1F1EA 1F1F7 ; fully-qualified # 🇪🇷 E2.0 flag: Eritrea +1F1EA 1F1F8 ; fully-qualified # 🇪🇸 E0.6 flag: Spain +1F1EA 1F1F9 ; fully-qualified # 🇪🇹 E2.0 flag: Ethiopia +1F1EA 1F1FA ; fully-qualified # 🇪🇺 E2.0 flag: European Union +1F1EB 1F1EE ; fully-qualified # 🇫🇮 E2.0 flag: Finland +1F1EB 1F1EF ; fully-qualified # 🇫🇯 E2.0 flag: Fiji +1F1EB 1F1F0 ; fully-qualified # 🇫🇰 E2.0 flag: Falkland Islands +1F1EB 1F1F2 ; fully-qualified # 🇫🇲 E2.0 flag: Micronesia +1F1EB 1F1F4 ; fully-qualified # 🇫🇴 E2.0 flag: Faroe Islands +1F1EB 1F1F7 ; fully-qualified # 🇫🇷 E0.6 flag: France +1F1EC 1F1E6 ; fully-qualified # 🇬🇦 E2.0 flag: Gabon +1F1EC 1F1E7 ; fully-qualified # 🇬🇧 E0.6 flag: United Kingdom +1F1EC 1F1E9 ; fully-qualified # 🇬🇩 E2.0 flag: Grenada +1F1EC 1F1EA ; fully-qualified # 🇬🇪 E2.0 flag: Georgia +1F1EC 1F1EB ; fully-qualified # 🇬🇫 E2.0 flag: French Guiana +1F1EC 1F1EC ; fully-qualified # 🇬🇬 E2.0 flag: Guernsey +1F1EC 1F1ED ; fully-qualified # 🇬🇭 E2.0 flag: Ghana +1F1EC 1F1EE ; fully-qualified # 🇬🇮 E2.0 flag: Gibraltar +1F1EC 1F1F1 ; fully-qualified # 🇬🇱 E2.0 flag: Greenland +1F1EC 1F1F2 ; fully-qualified # 🇬🇲 E2.0 flag: Gambia +1F1EC 1F1F3 ; fully-qualified # 🇬🇳 E2.0 flag: Guinea +1F1EC 1F1F5 ; fully-qualified # 🇬🇵 E2.0 flag: Guadeloupe +1F1EC 1F1F6 ; fully-qualified # 🇬🇶 E2.0 flag: Equatorial Guinea +1F1EC 1F1F7 ; fully-qualified # 🇬🇷 E2.0 flag: Greece +1F1EC 1F1F8 ; fully-qualified # 🇬🇸 E2.0 flag: South Georgia & South Sandwich Islands +1F1EC 1F1F9 ; fully-qualified # 🇬🇹 E2.0 flag: Guatemala +1F1EC 1F1FA ; fully-qualified # 🇬🇺 E2.0 flag: Guam +1F1EC 1F1FC ; fully-qualified # 🇬🇼 E2.0 flag: Guinea-Bissau +1F1EC 1F1FE ; fully-qualified # 🇬🇾 E2.0 flag: Guyana +1F1ED 1F1F0 ; fully-qualified # 🇭🇰 E2.0 flag: Hong Kong SAR China +1F1ED 1F1F2 ; fully-qualified # 🇭🇲 E2.0 flag: Heard & McDonald Islands +1F1ED 1F1F3 ; fully-qualified # 🇭🇳 E2.0 flag: Honduras +1F1ED 1F1F7 ; fully-qualified # 🇭🇷 E2.0 flag: Croatia +1F1ED 1F1F9 ; fully-qualified # 🇭🇹 E2.0 flag: Haiti +1F1ED 1F1FA ; fully-qualified # 🇭🇺 E2.0 flag: Hungary +1F1EE 1F1E8 ; fully-qualified # 🇮🇨 E2.0 flag: Canary Islands +1F1EE 1F1E9 ; fully-qualified # 🇮🇩 E2.0 flag: Indonesia +1F1EE 1F1EA ; fully-qualified # 🇮🇪 E2.0 flag: Ireland +1F1EE 1F1F1 ; fully-qualified # 🇮🇱 E2.0 flag: Israel +1F1EE 1F1F2 ; fully-qualified # 🇮🇲 E2.0 flag: Isle of Man +1F1EE 1F1F3 ; fully-qualified # 🇮🇳 E2.0 flag: India +1F1EE 1F1F4 ; fully-qualified # 🇮🇴 E2.0 flag: British Indian Ocean Territory +1F1EE 1F1F6 ; fully-qualified # 🇮🇶 E2.0 flag: Iraq +1F1EE 1F1F7 ; fully-qualified # 🇮🇷 E2.0 flag: Iran +1F1EE 1F1F8 ; fully-qualified # 🇮🇸 E2.0 flag: Iceland +1F1EE 1F1F9 ; fully-qualified # 🇮🇹 E0.6 flag: Italy +1F1EF 1F1EA ; fully-qualified # 🇯🇪 E2.0 flag: Jersey +1F1EF 1F1F2 ; fully-qualified # 🇯🇲 E2.0 flag: Jamaica +1F1EF 1F1F4 ; fully-qualified # 🇯🇴 E2.0 flag: Jordan +1F1EF 1F1F5 ; fully-qualified # 🇯🇵 E0.6 flag: Japan +1F1F0 1F1EA ; fully-qualified # 🇰🇪 E2.0 flag: Kenya +1F1F0 1F1EC ; fully-qualified # 🇰🇬 E2.0 flag: Kyrgyzstan +1F1F0 1F1ED ; fully-qualified # 🇰🇭 E2.0 flag: Cambodia +1F1F0 1F1EE ; fully-qualified # 🇰🇮 E2.0 flag: Kiribati +1F1F0 1F1F2 ; fully-qualified # 🇰🇲 E2.0 flag: Comoros +1F1F0 1F1F3 ; fully-qualified # 🇰🇳 E2.0 flag: St. Kitts & Nevis +1F1F0 1F1F5 ; fully-qualified # 🇰🇵 E2.0 flag: North Korea +1F1F0 1F1F7 ; fully-qualified # 🇰🇷 E0.6 flag: South Korea +1F1F0 1F1FC ; fully-qualified # 🇰🇼 E2.0 flag: Kuwait +1F1F0 1F1FE ; fully-qualified # 🇰🇾 E2.0 flag: Cayman Islands +1F1F0 1F1FF ; fully-qualified # 🇰🇿 E2.0 flag: Kazakhstan +1F1F1 1F1E6 ; fully-qualified # 🇱🇦 E2.0 flag: Laos +1F1F1 1F1E7 ; fully-qualified # 🇱🇧 E2.0 flag: Lebanon +1F1F1 1F1E8 ; fully-qualified # 🇱🇨 E2.0 flag: St. Lucia +1F1F1 1F1EE ; fully-qualified # 🇱🇮 E2.0 flag: Liechtenstein +1F1F1 1F1F0 ; fully-qualified # 🇱🇰 E2.0 flag: Sri Lanka +1F1F1 1F1F7 ; fully-qualified # 🇱🇷 E2.0 flag: Liberia +1F1F1 1F1F8 ; fully-qualified # 🇱🇸 E2.0 flag: Lesotho +1F1F1 1F1F9 ; fully-qualified # 🇱🇹 E2.0 flag: Lithuania +1F1F1 1F1FA ; fully-qualified # 🇱🇺 E2.0 flag: Luxembourg +1F1F1 1F1FB ; fully-qualified # 🇱🇻 E2.0 flag: Latvia +1F1F1 1F1FE ; fully-qualified # 🇱🇾 E2.0 flag: Libya +1F1F2 1F1E6 ; fully-qualified # 🇲🇦 E2.0 flag: Morocco +1F1F2 1F1E8 ; fully-qualified # 🇲🇨 E2.0 flag: Monaco +1F1F2 1F1E9 ; fully-qualified # 🇲🇩 E2.0 flag: Moldova +1F1F2 1F1EA ; fully-qualified # 🇲🇪 E2.0 flag: Montenegro +1F1F2 1F1EB ; fully-qualified # 🇲🇫 E2.0 flag: St. Martin +1F1F2 1F1EC ; fully-qualified # 🇲🇬 E2.0 flag: Madagascar +1F1F2 1F1ED ; fully-qualified # 🇲🇭 E2.0 flag: Marshall Islands +1F1F2 1F1F0 ; fully-qualified # 🇲🇰 E2.0 flag: North Macedonia +1F1F2 1F1F1 ; fully-qualified # 🇲🇱 E2.0 flag: Mali +1F1F2 1F1F2 ; fully-qualified # 🇲🇲 E2.0 flag: Myanmar (Burma) +1F1F2 1F1F3 ; fully-qualified # 🇲🇳 E2.0 flag: Mongolia +1F1F2 1F1F4 ; fully-qualified # 🇲🇴 E2.0 flag: Macao SAR China +1F1F2 1F1F5 ; fully-qualified # 🇲🇵 E2.0 flag: Northern Mariana Islands +1F1F2 1F1F6 ; fully-qualified # 🇲🇶 E2.0 flag: Martinique +1F1F2 1F1F7 ; fully-qualified # 🇲🇷 E2.0 flag: Mauritania +1F1F2 1F1F8 ; fully-qualified # 🇲🇸 E2.0 flag: Montserrat +1F1F2 1F1F9 ; fully-qualified # 🇲🇹 E2.0 flag: Malta +1F1F2 1F1FA ; fully-qualified # 🇲🇺 E2.0 flag: Mauritius +1F1F2 1F1FB ; fully-qualified # 🇲🇻 E2.0 flag: Maldives +1F1F2 1F1FC ; fully-qualified # 🇲🇼 E2.0 flag: Malawi +1F1F2 1F1FD ; fully-qualified # 🇲🇽 E2.0 flag: Mexico +1F1F2 1F1FE ; fully-qualified # 🇲🇾 E2.0 flag: Malaysia +1F1F2 1F1FF ; fully-qualified # 🇲🇿 E2.0 flag: Mozambique +1F1F3 1F1E6 ; fully-qualified # 🇳🇦 E2.0 flag: Namibia +1F1F3 1F1E8 ; fully-qualified # 🇳🇨 E2.0 flag: New Caledonia +1F1F3 1F1EA ; fully-qualified # 🇳🇪 E2.0 flag: Niger +1F1F3 1F1EB ; fully-qualified # 🇳🇫 E2.0 flag: Norfolk Island +1F1F3 1F1EC ; fully-qualified # 🇳🇬 E2.0 flag: Nigeria +1F1F3 1F1EE ; fully-qualified # 🇳🇮 E2.0 flag: Nicaragua +1F1F3 1F1F1 ; fully-qualified # 🇳🇱 E2.0 flag: Netherlands +1F1F3 1F1F4 ; fully-qualified # 🇳🇴 E2.0 flag: Norway +1F1F3 1F1F5 ; fully-qualified # 🇳🇵 E2.0 flag: Nepal +1F1F3 1F1F7 ; fully-qualified # 🇳🇷 E2.0 flag: Nauru +1F1F3 1F1FA ; fully-qualified # 🇳🇺 E2.0 flag: Niue +1F1F3 1F1FF ; fully-qualified # 🇳🇿 E2.0 flag: New Zealand +1F1F4 1F1F2 ; fully-qualified # 🇴🇲 E2.0 flag: Oman +1F1F5 1F1E6 ; fully-qualified # 🇵🇦 E2.0 flag: Panama +1F1F5 1F1EA ; fully-qualified # 🇵🇪 E2.0 flag: Peru +1F1F5 1F1EB ; fully-qualified # 🇵🇫 E2.0 flag: French Polynesia +1F1F5 1F1EC ; fully-qualified # 🇵🇬 E2.0 flag: Papua New Guinea +1F1F5 1F1ED ; fully-qualified # 🇵🇭 E2.0 flag: Philippines +1F1F5 1F1F0 ; fully-qualified # 🇵🇰 E2.0 flag: Pakistan +1F1F5 1F1F1 ; fully-qualified # 🇵🇱 E2.0 flag: Poland +1F1F5 1F1F2 ; fully-qualified # 🇵🇲 E2.0 flag: St. Pierre & Miquelon +1F1F5 1F1F3 ; fully-qualified # 🇵🇳 E2.0 flag: Pitcairn Islands +1F1F5 1F1F7 ; fully-qualified # 🇵🇷 E2.0 flag: Puerto Rico +1F1F5 1F1F8 ; fully-qualified # 🇵🇸 E2.0 flag: Palestinian Territories +1F1F5 1F1F9 ; fully-qualified # 🇵🇹 E2.0 flag: Portugal +1F1F5 1F1FC ; fully-qualified # 🇵🇼 E2.0 flag: Palau +1F1F5 1F1FE ; fully-qualified # 🇵🇾 E2.0 flag: Paraguay +1F1F6 1F1E6 ; fully-qualified # 🇶🇦 E2.0 flag: Qatar +1F1F7 1F1EA ; fully-qualified # 🇷🇪 E2.0 flag: Réunion +1F1F7 1F1F4 ; fully-qualified # 🇷🇴 E2.0 flag: Romania +1F1F7 1F1F8 ; fully-qualified # 🇷🇸 E2.0 flag: Serbia +1F1F7 1F1FA ; fully-qualified # 🇷🇺 E0.6 flag: Russia +1F1F7 1F1FC ; fully-qualified # 🇷🇼 E2.0 flag: Rwanda +1F1F8 1F1E6 ; fully-qualified # 🇸🇦 E2.0 flag: Saudi Arabia +1F1F8 1F1E7 ; fully-qualified # 🇸🇧 E2.0 flag: Solomon Islands +1F1F8 1F1E8 ; fully-qualified # 🇸🇨 E2.0 flag: Seychelles +1F1F8 1F1E9 ; fully-qualified # 🇸🇩 E2.0 flag: Sudan +1F1F8 1F1EA ; fully-qualified # 🇸🇪 E2.0 flag: Sweden +1F1F8 1F1EC ; fully-qualified # 🇸🇬 E2.0 flag: Singapore +1F1F8 1F1ED ; fully-qualified # 🇸🇭 E2.0 flag: St. Helena +1F1F8 1F1EE ; fully-qualified # 🇸🇮 E2.0 flag: Slovenia +1F1F8 1F1EF ; fully-qualified # 🇸🇯 E2.0 flag: Svalbard & Jan Mayen +1F1F8 1F1F0 ; fully-qualified # 🇸🇰 E2.0 flag: Slovakia +1F1F8 1F1F1 ; fully-qualified # 🇸🇱 E2.0 flag: Sierra Leone +1F1F8 1F1F2 ; fully-qualified # 🇸🇲 E2.0 flag: San Marino +1F1F8 1F1F3 ; fully-qualified # 🇸🇳 E2.0 flag: Senegal +1F1F8 1F1F4 ; fully-qualified # 🇸🇴 E2.0 flag: Somalia +1F1F8 1F1F7 ; fully-qualified # 🇸🇷 E2.0 flag: Suriname +1F1F8 1F1F8 ; fully-qualified # 🇸🇸 E2.0 flag: South Sudan +1F1F8 1F1F9 ; fully-qualified # 🇸🇹 E2.0 flag: São Tomé & Príncipe +1F1F8 1F1FB ; fully-qualified # 🇸🇻 E2.0 flag: El Salvador +1F1F8 1F1FD ; fully-qualified # 🇸🇽 E2.0 flag: Sint Maarten +1F1F8 1F1FE ; fully-qualified # 🇸🇾 E2.0 flag: Syria +1F1F8 1F1FF ; fully-qualified # 🇸🇿 E2.0 flag: Eswatini +1F1F9 1F1E6 ; fully-qualified # 🇹🇦 E2.0 flag: Tristan da Cunha +1F1F9 1F1E8 ; fully-qualified # 🇹🇨 E2.0 flag: Turks & Caicos Islands +1F1F9 1F1E9 ; fully-qualified # 🇹🇩 E2.0 flag: Chad +1F1F9 1F1EB ; fully-qualified # 🇹🇫 E2.0 flag: French Southern Territories +1F1F9 1F1EC ; fully-qualified # 🇹🇬 E2.0 flag: Togo +1F1F9 1F1ED ; fully-qualified # 🇹🇭 E2.0 flag: Thailand +1F1F9 1F1EF ; fully-qualified # 🇹🇯 E2.0 flag: Tajikistan +1F1F9 1F1F0 ; fully-qualified # 🇹🇰 E2.0 flag: Tokelau +1F1F9 1F1F1 ; fully-qualified # 🇹🇱 E2.0 flag: Timor-Leste +1F1F9 1F1F2 ; fully-qualified # 🇹🇲 E2.0 flag: Turkmenistan +1F1F9 1F1F3 ; fully-qualified # 🇹🇳 E2.0 flag: Tunisia +1F1F9 1F1F4 ; fully-qualified # 🇹🇴 E2.0 flag: Tonga +1F1F9 1F1F7 ; fully-qualified # 🇹🇷 E2.0 flag: Turkey +1F1F9 1F1F9 ; fully-qualified # 🇹🇹 E2.0 flag: Trinidad & Tobago +1F1F9 1F1FB ; fully-qualified # 🇹🇻 E2.0 flag: Tuvalu +1F1F9 1F1FC ; fully-qualified # 🇹🇼 E2.0 flag: Taiwan +1F1F9 1F1FF ; fully-qualified # 🇹🇿 E2.0 flag: Tanzania +1F1FA 1F1E6 ; fully-qualified # 🇺🇦 E2.0 flag: Ukraine +1F1FA 1F1EC ; fully-qualified # 🇺🇬 E2.0 flag: Uganda +1F1FA 1F1F2 ; fully-qualified # 🇺🇲 E2.0 flag: U.S. Outlying Islands +1F1FA 1F1F3 ; fully-qualified # 🇺🇳 E4.0 flag: United Nations +1F1FA 1F1F8 ; fully-qualified # 🇺🇸 E0.6 flag: United States +1F1FA 1F1FE ; fully-qualified # 🇺🇾 E2.0 flag: Uruguay +1F1FA 1F1FF ; fully-qualified # 🇺🇿 E2.0 flag: Uzbekistan +1F1FB 1F1E6 ; fully-qualified # 🇻🇦 E2.0 flag: Vatican City +1F1FB 1F1E8 ; fully-qualified # 🇻🇨 E2.0 flag: St. Vincent & Grenadines +1F1FB 1F1EA ; fully-qualified # 🇻🇪 E2.0 flag: Venezuela +1F1FB 1F1EC ; fully-qualified # 🇻🇬 E2.0 flag: British Virgin Islands +1F1FB 1F1EE ; fully-qualified # 🇻🇮 E2.0 flag: U.S. Virgin Islands +1F1FB 1F1F3 ; fully-qualified # 🇻🇳 E2.0 flag: Vietnam +1F1FB 1F1FA ; fully-qualified # 🇻🇺 E2.0 flag: Vanuatu +1F1FC 1F1EB ; fully-qualified # 🇼🇫 E2.0 flag: Wallis & Futuna +1F1FC 1F1F8 ; fully-qualified # 🇼🇸 E2.0 flag: Samoa +1F1FD 1F1F0 ; fully-qualified # 🇽🇰 E2.0 flag: Kosovo +1F1FE 1F1EA ; fully-qualified # 🇾🇪 E2.0 flag: Yemen +1F1FE 1F1F9 ; fully-qualified # 🇾🇹 E2.0 flag: Mayotte +1F1FF 1F1E6 ; fully-qualified # 🇿🇦 E2.0 flag: South Africa +1F1FF 1F1F2 ; fully-qualified # 🇿🇲 E2.0 flag: Zambia +1F1FF 1F1FC ; fully-qualified # 🇿🇼 E2.0 flag: Zimbabwe + +# subgroup: subdivision-flag +1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 E5.0 flag: England +1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 E5.0 flag: Scotland +1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴󠁧󠁢󠁷󠁬󠁳󠁿 E5.0 flag: Wales + +# Flags subtotal: 275 +# Flags subtotal: 275 w/o modifiers + +# Status Counts +# fully-qualified : 3290 +# minimally-qualified : 614 +# unqualified : 250 +# component : 9 + +#EOF diff --git a/resources/emoji.json b/resources/emoji.json deleted file mode 100644 index 791fa190..00000000 --- a/resources/emoji.json +++ /dev/null @@ -1 +0,0 @@ -{"grinning":{"unicode":"1f600","unicode_alt":"","code_decimal":"😀","name":"grinning face","shortname":":grinning:","category":"people","emoji_order":"1","aliases":[],"aliases_ascii":[],"keywords":["happy","smiley","emotion"]},"grin":{"unicode":"1f601","unicode_alt":"","code_decimal":"😁","name":"grinning face with smiling eyes","shortname":":grin:","category":"people","emoji_order":"2","aliases":[],"aliases_ascii":[],"keywords":["happy","silly","smiley","emotion","good","selfie"]},"joy":{"unicode":"1f602","unicode_alt":"","code_decimal":"😂","name":"face with tears of joy","shortname":":joy:","category":"people","emoji_order":"3","aliases":[],"aliases_ascii":[":')",":'-)"],"keywords":["happy","silly","smiley","cry","laugh","emotion","sarcastic"]},"rofl":{"unicode":"1f923","unicode_alt":"","code_decimal":"🤣","name":"rolling on the floor laughing","shortname":":rofl:","category":"people","emoji_order":"4","aliases":[":rolling_on_the_floor_laughing:"],"aliases_ascii":[],"keywords":[]},"smiley":{"unicode":"1f603","unicode_alt":"","code_decimal":"😃","name":"smiling face with open mouth","shortname":":smiley:","category":"people","emoji_order":"5","aliases":[],"aliases_ascii":[":D",":-D","=D"],"keywords":["happy","smiley","emotion","good"]},"smile":{"unicode":"1f604","unicode_alt":"","code_decimal":"😄","name":"smiling face with open mouth and smiling eyes","shortname":":smile:","category":"people","emoji_order":"6","aliases":[],"aliases_ascii":[],"keywords":["happy","smiley","emotion"]},"sweat_smile":{"unicode":"1f605","unicode_alt":"","code_decimal":"😅","name":"smiling face with open mouth and cold sweat","shortname":":sweat_smile:","category":"people","emoji_order":"7","aliases":[],"aliases_ascii":["':)","':-)","'=)","':D","':-D","'=D"],"keywords":["smiley","workout","sweat","emotion"]},"laughing":{"unicode":"1f606","unicode_alt":"","code_decimal":"😆","name":"smiling face with open mouth and tightly-closed eyes","shortname":":laughing:","category":"people","emoji_order":"8","aliases":[":satisfied:"],"aliases_ascii":[">:)",">;)",">:-)",">=)"],"keywords":["happy","smiley","laugh","emotion"]},"wink":{"unicode":"1f609","unicode_alt":"","code_decimal":"😉","name":"winking face","shortname":":wink:","category":"people","emoji_order":"9","aliases":[],"aliases_ascii":[";)",";-)","*-)","*)",";-]",";]",";D",";^)"],"keywords":["silly","smiley","emotion"]},"blush":{"unicode":"1f60a","unicode_alt":"","code_decimal":"😊","name":"smiling face with smiling eyes","shortname":":blush:","category":"people","emoji_order":"10","aliases":[],"aliases_ascii":[],"keywords":["happy","smiley","emotion","good","beautiful"]},"yum":{"unicode":"1f60b","unicode_alt":"","code_decimal":"😋","name":"face savouring delicious food","shortname":":yum:","category":"people","emoji_order":"11","aliases":[],"aliases_ascii":[],"keywords":["happy","silly","smiley","emotion","sarcastic","good"]},"sunglasses":{"unicode":"1f60e","unicode_alt":"","code_decimal":"😎","name":"smiling face with sunglasses","shortname":":sunglasses:","category":"people","emoji_order":"12","aliases":[],"aliases_ascii":["B-)","B)","8)","8-)","B-D","8-D"],"keywords":["silly","smiley","emojione","glasses","boys night"]},"heart_eyes":{"unicode":"1f60d","unicode_alt":"","code_decimal":"😍","name":"smiling face with heart-shaped eyes","shortname":":heart_eyes:","category":"people","emoji_order":"13","aliases":[],"aliases_ascii":[],"keywords":["happy","smiley","love","sex","heart eyes","emotion","beautiful"]},"kissing_heart":{"unicode":"1f618","unicode_alt":"","code_decimal":"😘","name":"face throwing a kiss","shortname":":kissing_heart:","category":"people","emoji_order":"14","aliases":[],"aliases_ascii":[":*",":-*","=*",":^*"],"keywords":["smiley","love","sexy"]},"kissing":{"unicode":"1f617","unicode_alt":"","code_decimal":"😗","name":"kissing face","shortname":":kissing:","category":"people","emoji_order":"15","aliases":[],"aliases_ascii":[],"keywords":["smiley","sexy"]},"kissing_smiling_eyes":{"unicode":"1f619","unicode_alt":"","code_decimal":"😙","name":"kissing face with smiling eyes","shortname":":kissing_smiling_eyes:","category":"people","emoji_order":"16","aliases":[],"aliases_ascii":[],"keywords":["smiley","sexy"]},"kissing_closed_eyes":{"unicode":"1f61a","unicode_alt":"","code_decimal":"😚","name":"kissing face with closed eyes","shortname":":kissing_closed_eyes:","category":"people","emoji_order":"17","aliases":[],"aliases_ascii":[],"keywords":["smiley","sexy"]},"relaxed":{"unicode":"263a","unicode_alt":"263a-fe0f","code_decimal":"☺","name":"white smiling face","shortname":":relaxed:","category":"people","emoji_order":"18","aliases":[],"aliases_ascii":[],"keywords":["happy","smiley"]},"slight_smile":{"unicode":"1f642","unicode_alt":"","code_decimal":"🙂","name":"slightly smiling face","shortname":":slight_smile:","category":"people","emoji_order":"19","aliases":[":slightly_smiling_face:"],"aliases_ascii":[":)",":-)","=]","=)",":]"],"keywords":["happy","smiley"]},"hugging":{"unicode":"1f917","unicode_alt":"","code_decimal":"🤗","name":"hugging face","shortname":":hugging:","category":"people","emoji_order":"20","aliases":[":hugging_face:"],"aliases_ascii":[],"keywords":["smiley","hug","thank you"]},"thinking":{"unicode":"1f914","unicode_alt":"","code_decimal":"🤔","name":"thinking face","shortname":":thinking:","category":"people","emoji_order":"21","aliases":[":thinking_face:"],"aliases_ascii":[],"keywords":["smiley","thinking","boys night"]},"neutral_face":{"unicode":"1f610","unicode_alt":"","code_decimal":"😐","name":"neutral face","shortname":":neutral_face:","category":"people","emoji_order":"22","aliases":[],"aliases_ascii":[],"keywords":["mad","smiley","shrug","neutral","emotion"]},"expressionless":{"unicode":"1f611","unicode_alt":"","code_decimal":"😑","name":"expressionless face","shortname":":expressionless:","category":"people","emoji_order":"23","aliases":[],"aliases_ascii":["-_-","-__-","-___-"],"keywords":["mad","smiley","neutral","emotion"]},"no_mouth":{"unicode":"1f636","unicode_alt":"","code_decimal":"😶","name":"face without mouth","shortname":":no_mouth:","category":"people","emoji_order":"24","aliases":[],"aliases_ascii":[":-X",":X",":-#",":#","=X","=x",":x",":-x","=#"],"keywords":["mad","smiley","neutral","emotion"]},"rolling_eyes":{"unicode":"1f644","unicode_alt":"","code_decimal":"🙄","name":"face with rolling eyes","shortname":":rolling_eyes:","category":"people","emoji_order":"25","aliases":[":face_with_rolling_eyes:"],"aliases_ascii":[],"keywords":["mad","smiley","rolling eyes","emotion","sarcastic"]},"smirk":{"unicode":"1f60f","unicode_alt":"","code_decimal":"😏","name":"smirking face","shortname":":smirk:","category":"people","emoji_order":"26","aliases":[],"aliases_ascii":[],"keywords":["silly","smiley","sexy","sarcastic"]},"persevere":{"unicode":"1f623","unicode_alt":"","code_decimal":"😣","name":"persevering face","shortname":":persevere:","category":"people","emoji_order":"27","aliases":[],"aliases_ascii":[">.<"],"keywords":["sad","smiley","angry","emotion"]},"disappointed_relieved":{"unicode":"1f625","unicode_alt":"","code_decimal":"😥","name":"disappointed but relieved face","shortname":":disappointed_relieved:","category":"people","emoji_order":"28","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","stressed","sweat","cry","emotion"]},"open_mouth":{"unicode":"1f62e","unicode_alt":"","code_decimal":"😮","name":"face with open mouth","shortname":":open_mouth:","category":"people","emoji_order":"29","aliases":[],"aliases_ascii":[":-O",":O",":-o",":o","O_O",">:O"],"keywords":["smiley","surprised","wow","emotion"]},"zipper_mouth":{"unicode":"1f910","unicode_alt":"","code_decimal":"🤐","name":"zipper-mouth face","shortname":":zipper_mouth:","category":"people","emoji_order":"30","aliases":[":zipper_mouth_face:"],"aliases_ascii":[],"keywords":["mad","smiley"]},"hushed":{"unicode":"1f62f","unicode_alt":"","code_decimal":"😯","name":"hushed face","shortname":":hushed:","category":"people","emoji_order":"31","aliases":[],"aliases_ascii":[],"keywords":["smiley","surprised","wow"]},"sleepy":{"unicode":"1f62a","unicode_alt":"","code_decimal":"😪","name":"sleepy face","shortname":":sleepy:","category":"people","emoji_order":"32","aliases":[],"aliases_ascii":[],"keywords":["smiley","sick","emotion"]},"tired_face":{"unicode":"1f62b","unicode_alt":"","code_decimal":"😫","name":"tired face","shortname":":tired_face:","category":"people","emoji_order":"33","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","tired","emotion"]},"sleeping":{"unicode":"1f634","unicode_alt":"","code_decimal":"😴","name":"sleeping face","shortname":":sleeping:","category":"people","emoji_order":"34","aliases":[],"aliases_ascii":[],"keywords":["smiley","tired","emotion","goodnight"]},"relieved":{"unicode":"1f60c","unicode_alt":"","code_decimal":"😌","name":"relieved face","shortname":":relieved:","category":"people","emoji_order":"35","aliases":[],"aliases_ascii":[],"keywords":["smiley","emotion"]},"nerd":{"unicode":"1f913","unicode_alt":"","code_decimal":"🤓","name":"nerd face","shortname":":nerd:","category":"people","emoji_order":"36","aliases":[":nerd_face:"],"aliases_ascii":[],"keywords":["smiley","glasses"]},"stuck_out_tongue":{"unicode":"1f61b","unicode_alt":"","code_decimal":"😛","name":"face with stuck-out tongue","shortname":":stuck_out_tongue:","category":"people","emoji_order":"37","aliases":[],"aliases_ascii":[":P",":-P","=P",":-p",":p","=p",":-\u00de",":\u00de",":\u00fe",":-\u00fe",":-b",":b","d:"],"keywords":["smiley","sex","emotion"]},"stuck_out_tongue_winking_eye":{"unicode":"1f61c","unicode_alt":"","code_decimal":"😜","name":"face with stuck-out tongue and winking eye","shortname":":stuck_out_tongue_winking_eye:","category":"people","emoji_order":"38","aliases":[],"aliases_ascii":[">:P","X-P","x-p"],"keywords":["happy","smiley","emotion","parties"]},"stuck_out_tongue_closed_eyes":{"unicode":"1f61d","unicode_alt":"","code_decimal":"😝","name":"face with stuck-out tongue and tightly-closed eyes","shortname":":stuck_out_tongue_closed_eyes:","category":"people","emoji_order":"39","aliases":[],"aliases_ascii":[],"keywords":["happy","smiley","emotion"]},"drooling_face":{"unicode":"1f924","unicode_alt":"","code_decimal":"🤤","name":"drooling face","shortname":":drooling_face:","category":"people","emoji_order":"40","aliases":[":drool:"],"aliases_ascii":[],"keywords":[]},"unamused":{"unicode":"1f612","unicode_alt":"","code_decimal":"😒","name":"unamused face","shortname":":unamused:","category":"people","emoji_order":"41","aliases":[],"aliases_ascii":[],"keywords":["sad","mad","smiley","tired","emotion"]},"sweat":{"unicode":"1f613","unicode_alt":"","code_decimal":"😓","name":"face with cold sweat","shortname":":sweat:","category":"people","emoji_order":"42","aliases":[],"aliases_ascii":["':(","':-(","'=("],"keywords":["sad","smiley","stressed","sweat","emotion"]},"pensive":{"unicode":"1f614","unicode_alt":"","code_decimal":"😔","name":"pensive face","shortname":":pensive:","category":"people","emoji_order":"43","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","emotion","rip"]},"confused":{"unicode":"1f615","unicode_alt":"","code_decimal":"😕","name":"confused face","shortname":":confused:","category":"people","emoji_order":"44","aliases":[],"aliases_ascii":[">:\\",">:\/",":-\/",":-.",":\/",":\\","=\/","=\\",":L","=L"],"keywords":["smiley","surprised","emotion"]},"upside_down":{"unicode":"1f643","unicode_alt":"","code_decimal":"🙃","name":"upside-down face","shortname":":upside_down:","category":"people","emoji_order":"45","aliases":[":upside_down_face:"],"aliases_ascii":[],"keywords":["silly","smiley","sarcastic"]},"money_mouth":{"unicode":"1f911","unicode_alt":"","code_decimal":"🤑","name":"money-mouth face","shortname":":money_mouth:","category":"people","emoji_order":"46","aliases":[":money_mouth_face:"],"aliases_ascii":[],"keywords":["smiley","win","money","emotion","boys night"]},"astonished":{"unicode":"1f632","unicode_alt":"","code_decimal":"😲","name":"astonished face","shortname":":astonished:","category":"people","emoji_order":"47","aliases":[],"aliases_ascii":[],"keywords":["smiley","surprised","wow","emotion","omg"]},"frowning2":{"unicode":"2639","unicode_alt":"2639-fe0f","code_decimal":"☹","name":"white frowning face","shortname":":frowning2:","category":"people","emoji_order":"48","aliases":[":white_frowning_face:"],"aliases_ascii":[],"keywords":["sad","smiley","emotion"]},"slight_frown":{"unicode":"1f641","unicode_alt":"","code_decimal":"🙁","name":"slightly frowning face","shortname":":slight_frown:","category":"people","emoji_order":"49","aliases":[":slightly_frowning_face:"],"aliases_ascii":[],"keywords":["sad","smiley","emotion"]},"confounded":{"unicode":"1f616","unicode_alt":"","code_decimal":"😖","name":"confounded face","shortname":":confounded:","category":"people","emoji_order":"50","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","angry","emotion"]},"disappointed":{"unicode":"1f61e","unicode_alt":"","code_decimal":"😞","name":"disappointed face","shortname":":disappointed:","category":"people","emoji_order":"51","aliases":[],"aliases_ascii":[">:[",":-(",":(",":-[",":[","=("],"keywords":["sad","smiley","tired","emotion"]},"worried":{"unicode":"1f61f","unicode_alt":"","code_decimal":"😟","name":"worried face","shortname":":worried:","category":"people","emoji_order":"52","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","emotion"]},"triumph":{"unicode":"1f624","unicode_alt":"","code_decimal":"😤","name":"face with look of triumph","shortname":":triumph:","category":"people","emoji_order":"53","aliases":[],"aliases_ascii":[],"keywords":["mad","smiley","angry","emotion","steam"]},"cry":{"unicode":"1f622","unicode_alt":"","code_decimal":"😢","name":"crying face","shortname":":cry:","category":"people","emoji_order":"54","aliases":[],"aliases_ascii":[":'(",":'-(",";(",";-("],"keywords":["sad","smiley","cry","emotion","rip","heartbreak"]},"sob":{"unicode":"1f62d","unicode_alt":"","code_decimal":"😭","name":"loudly crying face","shortname":":sob:","category":"people","emoji_order":"55","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","cry","emotion","heartbreak"]},"frowning":{"unicode":"1f626","unicode_alt":"","code_decimal":"😦","name":"frowning face with open mouth","shortname":":frowning:","category":"people","emoji_order":"56","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","surprised","emotion"]},"anguished":{"unicode":"1f627","unicode_alt":"","code_decimal":"😧","name":"anguished face","shortname":":anguished:","category":"people","emoji_order":"57","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","surprised","emotion"]},"fearful":{"unicode":"1f628","unicode_alt":"","code_decimal":"😨","name":"fearful face","shortname":":fearful:","category":"people","emoji_order":"58","aliases":[],"aliases_ascii":["D:"],"keywords":["smiley","surprised","emotion"]},"weary":{"unicode":"1f629","unicode_alt":"","code_decimal":"😩","name":"weary face","shortname":":weary:","category":"people","emoji_order":"59","aliases":[],"aliases_ascii":[],"keywords":["sad","smiley","tired","stressed","emotion"]},"grimacing":{"unicode":"1f62c","unicode_alt":"","code_decimal":"😬","name":"grimacing face","shortname":":grimacing:","category":"people","emoji_order":"60","aliases":[],"aliases_ascii":[],"keywords":["silly","smiley","emotion","selfie"]},"cold_sweat":{"unicode":"1f630","unicode_alt":"","code_decimal":"😰","name":"face with open mouth and cold sweat","shortname":":cold_sweat:","category":"people","emoji_order":"61","aliases":[],"aliases_ascii":[],"keywords":["smiley","sweat","emotion"]},"scream":{"unicode":"1f631","unicode_alt":"","code_decimal":"😱","name":"face screaming in fear","shortname":":scream:","category":"people","emoji_order":"62","aliases":[],"aliases_ascii":[],"keywords":["smiley","surprised","wow","emotion","omg"]},"flushed":{"unicode":"1f633","unicode_alt":"","code_decimal":"😳","name":"flushed face","shortname":":flushed:","category":"people","emoji_order":"63","aliases":[],"aliases_ascii":[":$","=$"],"keywords":["smiley","emotion","omg"]},"dizzy_face":{"unicode":"1f635","unicode_alt":"","code_decimal":"😵","name":"dizzy face","shortname":":dizzy_face:","category":"people","emoji_order":"64","aliases":[],"aliases_ascii":["#-)","#)","%-)","%)","X)","X-)"],"keywords":["smiley","surprised","dead","wow","emotion","omg"]},"rage":{"unicode":"1f621","unicode_alt":"","code_decimal":"😡","name":"pouting face","shortname":":rage:","category":"people","emoji_order":"65","aliases":[],"aliases_ascii":[],"keywords":["mad","smiley","angry","emotion"]},"angry":{"unicode":"1f620","unicode_alt":"","code_decimal":"😠","name":"angry face","shortname":":angry:","category":"people","emoji_order":"66","aliases":[],"aliases_ascii":[">:(",">:-(",":@"],"keywords":["mad","smiley","emotion"]},"innocent":{"unicode":"1f607","unicode_alt":"","code_decimal":"😇","name":"smiling face with halo","shortname":":innocent:","category":"people","emoji_order":"67","aliases":[],"aliases_ascii":["O:-)","0:-3","0:3","0:-)","0:)","0;^)","O:)","O;-)","O=)","0;-)","O:-3","O:3"],"keywords":["smiley","emotion"]},"cowboy":{"unicode":"1f920","unicode_alt":"","code_decimal":"🤠","name":"face with cowboy hat","shortname":":cowboy:","category":"people","emoji_order":"68","aliases":[":face_with_cowboy_hat:"],"aliases_ascii":[],"keywords":[]},"clown":{"unicode":"1f921","unicode_alt":"","code_decimal":"🤡","name":"clown face","shortname":":clown:","category":"people","emoji_order":"69","aliases":[":clown_face:"],"aliases_ascii":[],"keywords":[]},"lying_face":{"unicode":"1f925","unicode_alt":"","code_decimal":"🤥","name":"lying face","shortname":":lying_face:","category":"people","emoji_order":"70","aliases":[":liar:"],"aliases_ascii":[],"keywords":[]},"mask":{"unicode":"1f637","unicode_alt":"","code_decimal":"😷","name":"face with medical mask","shortname":":mask:","category":"people","emoji_order":"71","aliases":[],"aliases_ascii":[],"keywords":["smiley","dead","health","sick"]},"thermometer_face":{"unicode":"1f912","unicode_alt":"","code_decimal":"🤒","name":"face with thermometer","shortname":":thermometer_face:","category":"people","emoji_order":"72","aliases":[":face_with_thermometer:"],"aliases_ascii":[],"keywords":["smiley","health","sick","emotion"]},"head_bandage":{"unicode":"1f915","unicode_alt":"","code_decimal":"🤕","name":"face with head-bandage","shortname":":head_bandage:","category":"people","emoji_order":"73","aliases":[":face_with_head_bandage:"],"aliases_ascii":[],"keywords":["smiley","health","sick","emotion"]},"nauseated_face":{"unicode":"1f922","unicode_alt":"","code_decimal":"🤢","name":"nauseated face","shortname":":nauseated_face:","category":"people","emoji_order":"74","aliases":[":sick:"],"aliases_ascii":[],"keywords":[]},"sneezing_face":{"unicode":"1f927","unicode_alt":"","code_decimal":"🤧","name":"sneezing face","shortname":":sneezing_face:","category":"people","emoji_order":"75","aliases":[":sneeze:"],"aliases_ascii":[],"keywords":[]},"smiling_imp":{"unicode":"1f608","unicode_alt":"","code_decimal":"😈","name":"smiling face with horns","shortname":":smiling_imp:","category":"people","emoji_order":"76","aliases":[],"aliases_ascii":[],"keywords":["silly","smiley","angry","monster","devil","boys night"]},"imp":{"unicode":"1f47f","unicode_alt":"","code_decimal":"👿","name":"imp","shortname":":imp:","category":"people","emoji_order":"77","aliases":[],"aliases_ascii":[],"keywords":["smiley","monster","devil","wth"]},"japanese_ogre":{"unicode":"1f479","unicode_alt":"","code_decimal":"👹","name":"japanese ogre","shortname":":japanese_ogre:","category":"people","emoji_order":"78","aliases":[],"aliases_ascii":[],"keywords":["monster"]},"japanese_goblin":{"unicode":"1f47a","unicode_alt":"","code_decimal":"👺","name":"japanese goblin","shortname":":japanese_goblin:","category":"people","emoji_order":"79","aliases":[],"aliases_ascii":[],"keywords":["angry","monster"]},"skull":{"unicode":"1f480","unicode_alt":"","code_decimal":"💀","name":"skull","shortname":":skull:","category":"people","emoji_order":"80","aliases":[":skeleton:"],"aliases_ascii":[],"keywords":["dead","halloween","skull"]},"skull_crossbones":{"unicode":"2620","unicode_alt":"2620-fe0f","code_decimal":"☠","name":"skull and crossbones","shortname":":skull_crossbones:","category":"objects","emoji_order":"81","aliases":[":skull_and_crossbones:"],"aliases_ascii":[],"keywords":["symbol","dead","skull"]},"ghost":{"unicode":"1f47b","unicode_alt":"","code_decimal":"👻","name":"ghost","shortname":":ghost:","category":"people","emoji_order":"82","aliases":[],"aliases_ascii":[],"keywords":["holidays","halloween","monster"]},"alien":{"unicode":"1f47d","unicode_alt":"","code_decimal":"👽","name":"extraterrestrial alien","shortname":":alien:","category":"people","emoji_order":"83","aliases":[],"aliases_ascii":[],"keywords":["space","monster","alien","scientology"]},"space_invader":{"unicode":"1f47e","unicode_alt":"","code_decimal":"👾","name":"alien monster","shortname":":space_invader:","category":"activity","emoji_order":"84","aliases":[],"aliases_ascii":[],"keywords":["monster","alien"]},"robot":{"unicode":"1f916","unicode_alt":"","code_decimal":"🤖","name":"robot face","shortname":":robot:","category":"people","emoji_order":"85","aliases":[":robot_face:"],"aliases_ascii":[],"keywords":["monster","robot"]},"poop":{"unicode":"1f4a9","unicode_alt":"","code_decimal":"💩","name":"pile of poo","shortname":":poop:","category":"people","emoji_order":"86","aliases":[":shit:",":hankey:",":poo:"],"aliases_ascii":[],"keywords":["bathroom","shit","sol","diarrhea"]},"smiley_cat":{"unicode":"1f63a","unicode_alt":"","code_decimal":"😺","name":"smiling cat face with open mouth","shortname":":smiley_cat:","category":"people","emoji_order":"87","aliases":[],"aliases_ascii":[],"keywords":["happy","cat","animal"]},"smile_cat":{"unicode":"1f638","unicode_alt":"","code_decimal":"😸","name":"grinning cat face with smiling eyes","shortname":":smile_cat:","category":"people","emoji_order":"88","aliases":[],"aliases_ascii":[],"keywords":["happy","cat","animal"]},"joy_cat":{"unicode":"1f639","unicode_alt":"","code_decimal":"😹","name":"cat face with tears of joy","shortname":":joy_cat:","category":"people","emoji_order":"89","aliases":[],"aliases_ascii":[],"keywords":["happy","silly","cry","laugh","cat","animal","sarcastic"]},"heart_eyes_cat":{"unicode":"1f63b","unicode_alt":"","code_decimal":"😻","name":"smiling cat face with heart-shaped eyes","shortname":":heart_eyes_cat:","category":"people","emoji_order":"90","aliases":[],"aliases_ascii":[],"keywords":["heart eyes","cat","animal","beautiful"]},"smirk_cat":{"unicode":"1f63c","unicode_alt":"","code_decimal":"😼","name":"cat face with wry smile","shortname":":smirk_cat:","category":"people","emoji_order":"91","aliases":[],"aliases_ascii":[],"keywords":["cat","animal"]},"kissing_cat":{"unicode":"1f63d","unicode_alt":"","code_decimal":"😽","name":"kissing cat face with closed eyes","shortname":":kissing_cat:","category":"people","emoji_order":"92","aliases":[],"aliases_ascii":[],"keywords":["cat","animal"]},"scream_cat":{"unicode":"1f640","unicode_alt":"","code_decimal":"🙀","name":"weary cat face","shortname":":scream_cat:","category":"people","emoji_order":"93","aliases":[],"aliases_ascii":[],"keywords":["cat","animal"]},"crying_cat_face":{"unicode":"1f63f","unicode_alt":"","code_decimal":"😿","name":"crying cat face","shortname":":crying_cat_face:","category":"people","emoji_order":"94","aliases":[],"aliases_ascii":[],"keywords":["cry","cat","animal"]},"pouting_cat":{"unicode":"1f63e","unicode_alt":"","code_decimal":"😾","name":"pouting cat face","shortname":":pouting_cat:","category":"people","emoji_order":"95","aliases":[],"aliases_ascii":[],"keywords":["cat","animal"]},"see_no_evil":{"unicode":"1f648","unicode_alt":"","code_decimal":"🙈","name":"see-no-evil monkey","shortname":":see_no_evil:","category":"nature","emoji_order":"96","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"hear_no_evil":{"unicode":"1f649","unicode_alt":"","code_decimal":"🙉","name":"hear-no-evil monkey","shortname":":hear_no_evil:","category":"nature","emoji_order":"97","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"speak_no_evil":{"unicode":"1f64a","unicode_alt":"","code_decimal":"🙊","name":"speak-no-evil monkey","shortname":":speak_no_evil:","category":"nature","emoji_order":"98","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"boy":{"unicode":"1f466","unicode_alt":"","code_decimal":"👦","name":"boy","shortname":":boy:","category":"people","emoji_order":"99","aliases":[],"aliases_ascii":[],"keywords":["people","baby","diversity"]},"boy_tone1":{"unicode":"1f466-1f3fb","unicode_alt":"","code_decimal":"👦🏻","name":"boy tone 1","shortname":":boy_tone1:","category":"people","emoji_order":"100","aliases":[],"aliases_ascii":[],"keywords":[]},"boy_tone2":{"unicode":"1f466-1f3fc","unicode_alt":"","code_decimal":"👦🏼","name":"boy tone 2","shortname":":boy_tone2:","category":"people","emoji_order":"101","aliases":[],"aliases_ascii":[],"keywords":[]},"boy_tone3":{"unicode":"1f466-1f3fd","unicode_alt":"","code_decimal":"👦🏽","name":"boy tone 3","shortname":":boy_tone3:","category":"people","emoji_order":"102","aliases":[],"aliases_ascii":[],"keywords":[]},"boy_tone4":{"unicode":"1f466-1f3fe","unicode_alt":"","code_decimal":"👦🏾","name":"boy tone 4","shortname":":boy_tone4:","category":"people","emoji_order":"103","aliases":[],"aliases_ascii":[],"keywords":[]},"boy_tone5":{"unicode":"1f466-1f3ff","unicode_alt":"","code_decimal":"👦🏿","name":"boy tone 5","shortname":":boy_tone5:","category":"people","emoji_order":"104","aliases":[],"aliases_ascii":[],"keywords":[]},"girl":{"unicode":"1f467","unicode_alt":"","code_decimal":"👧","name":"girl","shortname":":girl:","category":"people","emoji_order":"105","aliases":[],"aliases_ascii":[],"keywords":["people","women","baby","diversity"]},"girl_tone1":{"unicode":"1f467-1f3fb","unicode_alt":"","code_decimal":"👧🏻","name":"girl tone 1","shortname":":girl_tone1:","category":"people","emoji_order":"106","aliases":[],"aliases_ascii":[],"keywords":[]},"girl_tone2":{"unicode":"1f467-1f3fc","unicode_alt":"","code_decimal":"👧🏼","name":"girl tone 2","shortname":":girl_tone2:","category":"people","emoji_order":"107","aliases":[],"aliases_ascii":[],"keywords":[]},"girl_tone3":{"unicode":"1f467-1f3fd","unicode_alt":"","code_decimal":"👧🏽","name":"girl tone 3","shortname":":girl_tone3:","category":"people","emoji_order":"108","aliases":[],"aliases_ascii":[],"keywords":[]},"girl_tone4":{"unicode":"1f467-1f3fe","unicode_alt":"","code_decimal":"👧🏾","name":"girl tone 4","shortname":":girl_tone4:","category":"people","emoji_order":"109","aliases":[],"aliases_ascii":[],"keywords":[]},"girl_tone5":{"unicode":"1f467-1f3ff","unicode_alt":"","code_decimal":"👧🏿","name":"girl tone 5","shortname":":girl_tone5:","category":"people","emoji_order":"110","aliases":[],"aliases_ascii":[],"keywords":[]},"man":{"unicode":"1f468","unicode_alt":"","code_decimal":"👨","name":"man","shortname":":man:","category":"people","emoji_order":"111","aliases":[],"aliases_ascii":[],"keywords":["people","men","sex","diversity","selfie","boys night"]},"man_tone1":{"unicode":"1f468-1f3fb","unicode_alt":"","code_decimal":"👨🏻","name":"man tone 1","shortname":":man_tone1:","category":"people","emoji_order":"112","aliases":[],"aliases_ascii":[],"keywords":[]},"man_tone2":{"unicode":"1f468-1f3fc","unicode_alt":"","code_decimal":"👨🏼","name":"man tone 2","shortname":":man_tone2:","category":"people","emoji_order":"113","aliases":[],"aliases_ascii":[],"keywords":[]},"man_tone3":{"unicode":"1f468-1f3fd","unicode_alt":"","code_decimal":"👨🏽","name":"man tone 3","shortname":":man_tone3:","category":"people","emoji_order":"114","aliases":[],"aliases_ascii":[],"keywords":[]},"man_tone4":{"unicode":"1f468-1f3fe","unicode_alt":"","code_decimal":"👨🏾","name":"man tone 4","shortname":":man_tone4:","category":"people","emoji_order":"115","aliases":[],"aliases_ascii":[],"keywords":[]},"man_tone5":{"unicode":"1f468-1f3ff","unicode_alt":"","code_decimal":"👨🏿","name":"man tone 5","shortname":":man_tone5:","category":"people","emoji_order":"116","aliases":[],"aliases_ascii":[],"keywords":[]},"woman":{"unicode":"1f469","unicode_alt":"","code_decimal":"👩","name":"woman","shortname":":woman:","category":"people","emoji_order":"117","aliases":[],"aliases_ascii":[],"keywords":["people","women","sex","diversity","feminist","selfie","girls night"]},"woman_tone1":{"unicode":"1f469-1f3fb","unicode_alt":"","code_decimal":"👩🏻","name":"woman tone 1","shortname":":woman_tone1:","category":"people","emoji_order":"118","aliases":[],"aliases_ascii":[],"keywords":[]},"woman_tone2":{"unicode":"1f469-1f3fc","unicode_alt":"","code_decimal":"👩🏼","name":"woman tone 2","shortname":":woman_tone2:","category":"people","emoji_order":"119","aliases":[],"aliases_ascii":[],"keywords":[]},"woman_tone3":{"unicode":"1f469-1f3fd","unicode_alt":"","code_decimal":"👩🏽","name":"woman tone 3","shortname":":woman_tone3:","category":"people","emoji_order":"120","aliases":[],"aliases_ascii":[],"keywords":[]},"woman_tone4":{"unicode":"1f469-1f3fe","unicode_alt":"","code_decimal":"👩🏾","name":"woman tone 4","shortname":":woman_tone4:","category":"people","emoji_order":"121","aliases":[],"aliases_ascii":[],"keywords":[]},"woman_tone5":{"unicode":"1f469-1f3ff","unicode_alt":"","code_decimal":"👩🏿","name":"woman tone 5","shortname":":woman_tone5:","category":"people","emoji_order":"122","aliases":[],"aliases_ascii":[],"keywords":[]},"older_man":{"unicode":"1f474","unicode_alt":"","code_decimal":"👴","name":"older man","shortname":":older_man:","category":"people","emoji_order":"123","aliases":[],"aliases_ascii":[],"keywords":["people","men","old people","diversity"]},"older_man_tone1":{"unicode":"1f474-1f3fb","unicode_alt":"","code_decimal":"👴🏻","name":"older man tone 1","shortname":":older_man_tone1:","category":"people","emoji_order":"124","aliases":[],"aliases_ascii":[],"keywords":[]},"older_man_tone2":{"unicode":"1f474-1f3fc","unicode_alt":"","code_decimal":"👴🏼","name":"older man tone 2","shortname":":older_man_tone2:","category":"people","emoji_order":"125","aliases":[],"aliases_ascii":[],"keywords":[]},"older_man_tone3":{"unicode":"1f474-1f3fd","unicode_alt":"","code_decimal":"👴🏽","name":"older man tone 3","shortname":":older_man_tone3:","category":"people","emoji_order":"126","aliases":[],"aliases_ascii":[],"keywords":[]},"older_man_tone4":{"unicode":"1f474-1f3fe","unicode_alt":"","code_decimal":"👴🏾","name":"older man tone 4","shortname":":older_man_tone4:","category":"people","emoji_order":"127","aliases":[],"aliases_ascii":[],"keywords":[]},"older_man_tone5":{"unicode":"1f474-1f3ff","unicode_alt":"","code_decimal":"👴🏿","name":"older man tone 5","shortname":":older_man_tone5:","category":"people","emoji_order":"128","aliases":[],"aliases_ascii":[],"keywords":[]},"older_woman":{"unicode":"1f475","unicode_alt":"","code_decimal":"👵","name":"older woman","shortname":":older_woman:","category":"people","emoji_order":"129","aliases":[":grandma:"],"aliases_ascii":[],"keywords":["people","old people","diversity"]},"older_woman_tone1":{"unicode":"1f475-1f3fb","unicode_alt":"","code_decimal":"👵🏻","name":"older woman tone 1","shortname":":older_woman_tone1:","category":"people","emoji_order":"130","aliases":[":grandma_tone1:"],"aliases_ascii":[],"keywords":[]},"older_woman_tone2":{"unicode":"1f475-1f3fc","unicode_alt":"","code_decimal":"👵🏼","name":"older woman tone 2","shortname":":older_woman_tone2:","category":"people","emoji_order":"131","aliases":[":grandma_tone2:"],"aliases_ascii":[],"keywords":[]},"older_woman_tone3":{"unicode":"1f475-1f3fd","unicode_alt":"","code_decimal":"👵🏽","name":"older woman tone 3","shortname":":older_woman_tone3:","category":"people","emoji_order":"132","aliases":[":grandma_tone3:"],"aliases_ascii":[],"keywords":[]},"older_woman_tone4":{"unicode":"1f475-1f3fe","unicode_alt":"","code_decimal":"👵🏾","name":"older woman tone 4","shortname":":older_woman_tone4:","category":"people","emoji_order":"133","aliases":[":grandma_tone4:"],"aliases_ascii":[],"keywords":[]},"older_woman_tone5":{"unicode":"1f475-1f3ff","unicode_alt":"","code_decimal":"👵🏿","name":"older woman tone 5","shortname":":older_woman_tone5:","category":"people","emoji_order":"134","aliases":[":grandma_tone5:"],"aliases_ascii":[],"keywords":[]},"baby":{"unicode":"1f476","unicode_alt":"","code_decimal":"👶","name":"baby","shortname":":baby:","category":"people","emoji_order":"135","aliases":[],"aliases_ascii":[],"keywords":["people","baby","diversity"]},"baby_tone1":{"unicode":"1f476-1f3fb","unicode_alt":"","code_decimal":"👶🏻","name":"baby tone 1","shortname":":baby_tone1:","category":"people","emoji_order":"136","aliases":[],"aliases_ascii":[],"keywords":[]},"baby_tone2":{"unicode":"1f476-1f3fc","unicode_alt":"","code_decimal":"👶🏼","name":"baby tone 2","shortname":":baby_tone2:","category":"people","emoji_order":"137","aliases":[],"aliases_ascii":[],"keywords":[]},"baby_tone3":{"unicode":"1f476-1f3fd","unicode_alt":"","code_decimal":"👶🏽","name":"baby tone 3","shortname":":baby_tone3:","category":"people","emoji_order":"138","aliases":[],"aliases_ascii":[],"keywords":[]},"baby_tone4":{"unicode":"1f476-1f3fe","unicode_alt":"","code_decimal":"👶🏾","name":"baby tone 4","shortname":":baby_tone4:","category":"people","emoji_order":"139","aliases":[],"aliases_ascii":[],"keywords":[]},"baby_tone5":{"unicode":"1f476-1f3ff","unicode_alt":"","code_decimal":"👶🏿","name":"baby tone 5","shortname":":baby_tone5:","category":"people","emoji_order":"140","aliases":[],"aliases_ascii":[],"keywords":[]},"angel":{"unicode":"1f47c","unicode_alt":"","code_decimal":"👼","name":"baby angel","shortname":":angel:","category":"people","emoji_order":"141","aliases":[],"aliases_ascii":[],"keywords":["people","diversity","omg"]},"angel_tone1":{"unicode":"1f47c-1f3fb","unicode_alt":"","code_decimal":"👼🏻","name":"baby angel tone 1","shortname":":angel_tone1:","category":"people","emoji_order":"142","aliases":[],"aliases_ascii":[],"keywords":[]},"angel_tone2":{"unicode":"1f47c-1f3fc","unicode_alt":"","code_decimal":"👼🏼","name":"baby angel tone 2","shortname":":angel_tone2:","category":"people","emoji_order":"143","aliases":[],"aliases_ascii":[],"keywords":[]},"angel_tone3":{"unicode":"1f47c-1f3fd","unicode_alt":"","code_decimal":"👼🏽","name":"baby angel tone 3","shortname":":angel_tone3:","category":"people","emoji_order":"144","aliases":[],"aliases_ascii":[],"keywords":[]},"angel_tone4":{"unicode":"1f47c-1f3fe","unicode_alt":"","code_decimal":"👼🏾","name":"baby angel tone 4","shortname":":angel_tone4:","category":"people","emoji_order":"145","aliases":[],"aliases_ascii":[],"keywords":[]},"angel_tone5":{"unicode":"1f47c-1f3ff","unicode_alt":"","code_decimal":"👼🏿","name":"baby angel tone 5","shortname":":angel_tone5:","category":"people","emoji_order":"146","aliases":[],"aliases_ascii":[],"keywords":[]},"cop":{"unicode":"1f46e","unicode_alt":"","code_decimal":"👮","name":"police officer","shortname":":cop:","category":"people","emoji_order":"339","aliases":[],"aliases_ascii":[],"keywords":["people","hat","men","diversity","job","police","911"]},"cop_tone1":{"unicode":"1f46e-1f3fb","unicode_alt":"","code_decimal":"👮🏻","name":"police officer tone 1","shortname":":cop_tone1:","category":"people","emoji_order":"340","aliases":[],"aliases_ascii":[],"keywords":[]},"cop_tone2":{"unicode":"1f46e-1f3fc","unicode_alt":"","code_decimal":"👮🏼","name":"police officer tone 2","shortname":":cop_tone2:","category":"people","emoji_order":"341","aliases":[],"aliases_ascii":[],"keywords":[]},"cop_tone3":{"unicode":"1f46e-1f3fd","unicode_alt":"","code_decimal":"👮🏽","name":"police officer tone 3","shortname":":cop_tone3:","category":"people","emoji_order":"342","aliases":[],"aliases_ascii":[],"keywords":[]},"cop_tone4":{"unicode":"1f46e-1f3fe","unicode_alt":"","code_decimal":"👮🏾","name":"police officer tone 4","shortname":":cop_tone4:","category":"people","emoji_order":"343","aliases":[],"aliases_ascii":[],"keywords":[]},"cop_tone5":{"unicode":"1f46e-1f3ff","unicode_alt":"","code_decimal":"👮🏿","name":"police officer tone 5","shortname":":cop_tone5:","category":"people","emoji_order":"344","aliases":[],"aliases_ascii":[],"keywords":[]},"spy":{"unicode":"1f575","unicode_alt":"1f575-fe0f","code_decimal":"🕵","name":"sleuth or spy","shortname":":spy:","category":"people","emoji_order":"357","aliases":[":sleuth_or_spy:"],"aliases_ascii":[],"keywords":["people","hat","men","glasses","diversity","job"]},"spy_tone1":{"unicode":"1f575-1f3fb","unicode_alt":"","code_decimal":"🕵🏻","name":"sleuth or spy tone 1","shortname":":spy_tone1:","category":"people","emoji_order":"358","aliases":[":sleuth_or_spy_tone1:"],"aliases_ascii":[],"keywords":[]},"spy_tone2":{"unicode":"1f575-1f3fc","unicode_alt":"","code_decimal":"🕵🏼","name":"sleuth or spy tone 2","shortname":":spy_tone2:","category":"people","emoji_order":"359","aliases":[":sleuth_or_spy_tone2:"],"aliases_ascii":[],"keywords":[]},"spy_tone3":{"unicode":"1f575-1f3fd","unicode_alt":"","code_decimal":"🕵🏽","name":"sleuth or spy tone 3","shortname":":spy_tone3:","category":"people","emoji_order":"360","aliases":[":sleuth_or_spy_tone3:"],"aliases_ascii":[],"keywords":[]},"spy_tone4":{"unicode":"1f575-1f3fe","unicode_alt":"","code_decimal":"🕵🏾","name":"sleuth or spy tone 4","shortname":":spy_tone4:","category":"people","emoji_order":"361","aliases":[":sleuth_or_spy_tone4:"],"aliases_ascii":[],"keywords":[]},"spy_tone5":{"unicode":"1f575-1f3ff","unicode_alt":"","code_decimal":"🕵🏿","name":"sleuth or spy tone 5","shortname":":spy_tone5:","category":"people","emoji_order":"362","aliases":[":sleuth_or_spy_tone5:"],"aliases_ascii":[],"keywords":[]},"guardsman":{"unicode":"1f482","unicode_alt":"","code_decimal":"💂","name":"guardsman","shortname":":guardsman:","category":"people","emoji_order":"375","aliases":[],"aliases_ascii":[],"keywords":["people","hat","men","diversity","job"]},"guardsman_tone1":{"unicode":"1f482-1f3fb","unicode_alt":"","code_decimal":"💂🏻","name":"guardsman tone 1","shortname":":guardsman_tone1:","category":"people","emoji_order":"376","aliases":[],"aliases_ascii":[],"keywords":[]},"guardsman_tone2":{"unicode":"1f482-1f3fc","unicode_alt":"","code_decimal":"💂🏼","name":"guardsman tone 2","shortname":":guardsman_tone2:","category":"people","emoji_order":"377","aliases":[],"aliases_ascii":[],"keywords":[]},"guardsman_tone3":{"unicode":"1f482-1f3fd","unicode_alt":"","code_decimal":"💂🏽","name":"guardsman tone 3","shortname":":guardsman_tone3:","category":"people","emoji_order":"378","aliases":[],"aliases_ascii":[],"keywords":[]},"guardsman_tone4":{"unicode":"1f482-1f3fe","unicode_alt":"","code_decimal":"💂🏾","name":"guardsman tone 4","shortname":":guardsman_tone4:","category":"people","emoji_order":"379","aliases":[],"aliases_ascii":[],"keywords":[]},"guardsman_tone5":{"unicode":"1f482-1f3ff","unicode_alt":"","code_decimal":"💂🏿","name":"guardsman tone 5","shortname":":guardsman_tone5:","category":"people","emoji_order":"380","aliases":[],"aliases_ascii":[],"keywords":[]},"construction_worker":{"unicode":"1f477","unicode_alt":"","code_decimal":"👷","name":"construction worker","shortname":":construction_worker:","category":"people","emoji_order":"393","aliases":[],"aliases_ascii":[],"keywords":["people","hat","men","diversity","job"]},"construction_worker_tone1":{"unicode":"1f477-1f3fb","unicode_alt":"","code_decimal":"👷🏻","name":"construction worker tone 1","shortname":":construction_worker_tone1:","category":"people","emoji_order":"394","aliases":[],"aliases_ascii":[],"keywords":[]},"construction_worker_tone2":{"unicode":"1f477-1f3fc","unicode_alt":"","code_decimal":"👷🏼","name":"construction worker tone 2","shortname":":construction_worker_tone2:","category":"people","emoji_order":"395","aliases":[],"aliases_ascii":[],"keywords":[]},"construction_worker_tone3":{"unicode":"1f477-1f3fd","unicode_alt":"","code_decimal":"👷🏽","name":"construction worker tone 3","shortname":":construction_worker_tone3:","category":"people","emoji_order":"396","aliases":[],"aliases_ascii":[],"keywords":[]},"construction_worker_tone4":{"unicode":"1f477-1f3fe","unicode_alt":"","code_decimal":"👷🏾","name":"construction worker tone 4","shortname":":construction_worker_tone4:","category":"people","emoji_order":"397","aliases":[],"aliases_ascii":[],"keywords":[]},"construction_worker_tone5":{"unicode":"1f477-1f3ff","unicode_alt":"","code_decimal":"👷🏿","name":"construction worker tone 5","shortname":":construction_worker_tone5:","category":"people","emoji_order":"398","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_turban":{"unicode":"1f473","unicode_alt":"","code_decimal":"👳","name":"man with turban","shortname":":man_with_turban:","category":"people","emoji_order":"411","aliases":[],"aliases_ascii":[],"keywords":["people","hat","diversity"]},"man_with_turban_tone1":{"unicode":"1f473-1f3fb","unicode_alt":"","code_decimal":"👳🏻","name":"man with turban tone 1","shortname":":man_with_turban_tone1:","category":"people","emoji_order":"412","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_turban_tone2":{"unicode":"1f473-1f3fc","unicode_alt":"","code_decimal":"👳🏼","name":"man with turban tone 2","shortname":":man_with_turban_tone2:","category":"people","emoji_order":"413","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_turban_tone3":{"unicode":"1f473-1f3fd","unicode_alt":"","code_decimal":"👳🏽","name":"man with turban tone 3","shortname":":man_with_turban_tone3:","category":"people","emoji_order":"414","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_turban_tone4":{"unicode":"1f473-1f3fe","unicode_alt":"","code_decimal":"👳🏾","name":"man with turban tone 4","shortname":":man_with_turban_tone4:","category":"people","emoji_order":"415","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_turban_tone5":{"unicode":"1f473-1f3ff","unicode_alt":"","code_decimal":"👳🏿","name":"man with turban tone 5","shortname":":man_with_turban_tone5:","category":"people","emoji_order":"416","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_blond_hair":{"unicode":"1f471","unicode_alt":"","code_decimal":"👱","name":"person with blond hair","shortname":":person_with_blond_hair:","category":"people","emoji_order":"429","aliases":[],"aliases_ascii":[],"keywords":["people","men","diversity"]},"person_with_blond_hair_tone1":{"unicode":"1f471-1f3fb","unicode_alt":"","code_decimal":"👱🏻","name":"person with blond hair tone 1","shortname":":person_with_blond_hair_tone1:","category":"people","emoji_order":"430","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_blond_hair_tone2":{"unicode":"1f471-1f3fc","unicode_alt":"","code_decimal":"👱🏼","name":"person with blond hair tone 2","shortname":":person_with_blond_hair_tone2:","category":"people","emoji_order":"431","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_blond_hair_tone3":{"unicode":"1f471-1f3fd","unicode_alt":"","code_decimal":"👱🏽","name":"person with blond hair tone 3","shortname":":person_with_blond_hair_tone3:","category":"people","emoji_order":"432","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_blond_hair_tone4":{"unicode":"1f471-1f3fe","unicode_alt":"","code_decimal":"👱🏾","name":"person with blond hair tone 4","shortname":":person_with_blond_hair_tone4:","category":"people","emoji_order":"433","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_blond_hair_tone5":{"unicode":"1f471-1f3ff","unicode_alt":"","code_decimal":"👱🏿","name":"person with blond hair tone 5","shortname":":person_with_blond_hair_tone5:","category":"people","emoji_order":"434","aliases":[],"aliases_ascii":[],"keywords":[]},"santa":{"unicode":"1f385","unicode_alt":"","code_decimal":"🎅","name":"father christmas","shortname":":santa:","category":"people","emoji_order":"447","aliases":[],"aliases_ascii":[],"keywords":["people","hat","winter","holidays","christmas","diversity","santa"]},"santa_tone1":{"unicode":"1f385-1f3fb","unicode_alt":"","code_decimal":"🎅🏻","name":"father christmas tone 1","shortname":":santa_tone1:","category":"people","emoji_order":"448","aliases":[],"aliases_ascii":[],"keywords":[]},"santa_tone2":{"unicode":"1f385-1f3fc","unicode_alt":"","code_decimal":"🎅🏼","name":"father christmas tone 2","shortname":":santa_tone2:","category":"people","emoji_order":"449","aliases":[],"aliases_ascii":[],"keywords":[]},"santa_tone3":{"unicode":"1f385-1f3fd","unicode_alt":"","code_decimal":"🎅🏽","name":"father christmas tone 3","shortname":":santa_tone3:","category":"people","emoji_order":"450","aliases":[],"aliases_ascii":[],"keywords":[]},"santa_tone4":{"unicode":"1f385-1f3fe","unicode_alt":"","code_decimal":"🎅🏾","name":"father christmas tone 4","shortname":":santa_tone4:","category":"people","emoji_order":"451","aliases":[],"aliases_ascii":[],"keywords":[]},"santa_tone5":{"unicode":"1f385-1f3ff","unicode_alt":"","code_decimal":"🎅🏿","name":"father christmas tone 5","shortname":":santa_tone5:","category":"people","emoji_order":"452","aliases":[],"aliases_ascii":[],"keywords":[]},"mrs_claus":{"unicode":"1f936","unicode_alt":"","code_decimal":"🤶","name":"mother christmas","shortname":":mrs_claus:","category":"people","emoji_order":"453","aliases":[":mother_christmas:"],"aliases_ascii":[],"keywords":[]},"mrs_claus_tone1":{"unicode":"1f936-1f3fb","unicode_alt":"","code_decimal":"🤶🏻","name":"mother christmas tone 1","shortname":":mrs_claus_tone1:","category":"people","emoji_order":"454","aliases":[":mother_christmas_tone1:"],"aliases_ascii":[],"keywords":[]},"mrs_claus_tone2":{"unicode":"1f936-1f3fc","unicode_alt":"","code_decimal":"🤶🏼","name":"mother christmas tone 2","shortname":":mrs_claus_tone2:","category":"people","emoji_order":"455","aliases":[":mother_christmas_tone2:"],"aliases_ascii":[],"keywords":[]},"mrs_claus_tone3":{"unicode":"1f936-1f3fd","unicode_alt":"","code_decimal":"🤶🏽","name":"mother christmas tone 3","shortname":":mrs_claus_tone3:","category":"people","emoji_order":"456","aliases":[":mother_christmas_tone3:"],"aliases_ascii":[],"keywords":[]},"mrs_claus_tone4":{"unicode":"1f936-1f3fe","unicode_alt":"","code_decimal":"🤶🏾","name":"mother christmas tone 4","shortname":":mrs_claus_tone4:","category":"people","emoji_order":"457","aliases":[":mother_christmas_tone4:"],"aliases_ascii":[],"keywords":[]},"mrs_claus_tone5":{"unicode":"1f936-1f3ff","unicode_alt":"","code_decimal":"🤶🏿","name":"mother christmas tone 5","shortname":":mrs_claus_tone5:","category":"people","emoji_order":"458","aliases":[":mother_christmas_tone5:"],"aliases_ascii":[],"keywords":[]},"princess":{"unicode":"1f478","unicode_alt":"","code_decimal":"👸","name":"princess","shortname":":princess:","category":"people","emoji_order":"459","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity","beautiful","girls night"]},"princess_tone1":{"unicode":"1f478-1f3fb","unicode_alt":"","code_decimal":"👸🏻","name":"princess tone 1","shortname":":princess_tone1:","category":"people","emoji_order":"460","aliases":[],"aliases_ascii":[],"keywords":[]},"princess_tone2":{"unicode":"1f478-1f3fc","unicode_alt":"","code_decimal":"👸🏼","name":"princess tone 2","shortname":":princess_tone2:","category":"people","emoji_order":"461","aliases":[],"aliases_ascii":[],"keywords":[]},"princess_tone3":{"unicode":"1f478-1f3fd","unicode_alt":"","code_decimal":"👸🏽","name":"princess tone 3","shortname":":princess_tone3:","category":"people","emoji_order":"462","aliases":[],"aliases_ascii":[],"keywords":[]},"princess_tone4":{"unicode":"1f478-1f3fe","unicode_alt":"","code_decimal":"👸🏾","name":"princess tone 4","shortname":":princess_tone4:","category":"people","emoji_order":"463","aliases":[],"aliases_ascii":[],"keywords":[]},"princess_tone5":{"unicode":"1f478-1f3ff","unicode_alt":"","code_decimal":"👸🏿","name":"princess tone 5","shortname":":princess_tone5:","category":"people","emoji_order":"464","aliases":[],"aliases_ascii":[],"keywords":[]},"prince":{"unicode":"1f934","unicode_alt":"","code_decimal":"🤴","name":"prince","shortname":":prince:","category":"people","emoji_order":"465","aliases":[],"aliases_ascii":[],"keywords":[]},"prince_tone1":{"unicode":"1f934-1f3fb","unicode_alt":"","code_decimal":"🤴🏻","name":"prince tone 1","shortname":":prince_tone1:","category":"people","emoji_order":"466","aliases":[],"aliases_ascii":[],"keywords":[]},"prince_tone2":{"unicode":"1f934-1f3fc","unicode_alt":"","code_decimal":"🤴🏼","name":"prince tone 2","shortname":":prince_tone2:","category":"people","emoji_order":"467","aliases":[],"aliases_ascii":[],"keywords":[]},"prince_tone3":{"unicode":"1f934-1f3fd","unicode_alt":"","code_decimal":"🤴🏽","name":"prince tone 3","shortname":":prince_tone3:","category":"people","emoji_order":"468","aliases":[],"aliases_ascii":[],"keywords":[]},"prince_tone4":{"unicode":"1f934-1f3fe","unicode_alt":"","code_decimal":"🤴🏾","name":"prince tone 4","shortname":":prince_tone4:","category":"people","emoji_order":"469","aliases":[],"aliases_ascii":[],"keywords":[]},"prince_tone5":{"unicode":"1f934-1f3ff","unicode_alt":"","code_decimal":"🤴🏿","name":"prince tone 5","shortname":":prince_tone5:","category":"people","emoji_order":"470","aliases":[],"aliases_ascii":[],"keywords":[]},"bride_with_veil":{"unicode":"1f470","unicode_alt":"","code_decimal":"👰","name":"bride with veil","shortname":":bride_with_veil:","category":"people","emoji_order":"471","aliases":[],"aliases_ascii":[],"keywords":["people","wedding","women","diversity"]},"bride_with_veil_tone1":{"unicode":"1f470-1f3fb","unicode_alt":"","code_decimal":"👰🏻","name":"bride with veil tone 1","shortname":":bride_with_veil_tone1:","category":"people","emoji_order":"472","aliases":[],"aliases_ascii":[],"keywords":[]},"bride_with_veil_tone2":{"unicode":"1f470-1f3fc","unicode_alt":"","code_decimal":"👰🏼","name":"bride with veil tone 2","shortname":":bride_with_veil_tone2:","category":"people","emoji_order":"473","aliases":[],"aliases_ascii":[],"keywords":[]},"bride_with_veil_tone3":{"unicode":"1f470-1f3fd","unicode_alt":"","code_decimal":"👰🏽","name":"bride with veil tone 3","shortname":":bride_with_veil_tone3:","category":"people","emoji_order":"474","aliases":[],"aliases_ascii":[],"keywords":[]},"bride_with_veil_tone4":{"unicode":"1f470-1f3fe","unicode_alt":"","code_decimal":"👰🏾","name":"bride with veil tone 4","shortname":":bride_with_veil_tone4:","category":"people","emoji_order":"475","aliases":[],"aliases_ascii":[],"keywords":[]},"bride_with_veil_tone5":{"unicode":"1f470-1f3ff","unicode_alt":"","code_decimal":"👰🏿","name":"bride with veil tone 5","shortname":":bride_with_veil_tone5:","category":"people","emoji_order":"476","aliases":[],"aliases_ascii":[],"keywords":[]},"man_in_tuxedo":{"unicode":"1f935","unicode_alt":"","code_decimal":"🤵","name":"man in tuxedo","shortname":":man_in_tuxedo:","category":"people","emoji_order":"477","aliases":[],"aliases_ascii":[],"keywords":[]},"man_in_tuxedo_tone1":{"unicode":"1f935-1f3fb","unicode_alt":"","code_decimal":"🤵🏻","name":"man in tuxedo tone 1","shortname":":man_in_tuxedo_tone1:","category":"people","emoji_order":"478","aliases":[":tuxedo_tone1:"],"aliases_ascii":[],"keywords":[]},"man_in_tuxedo_tone2":{"unicode":"1f935-1f3fc","unicode_alt":"","code_decimal":"🤵🏼","name":"man in tuxedo tone 2","shortname":":man_in_tuxedo_tone2:","category":"people","emoji_order":"479","aliases":[":tuxedo_tone2:"],"aliases_ascii":[],"keywords":[]},"man_in_tuxedo_tone3":{"unicode":"1f935-1f3fd","unicode_alt":"","code_decimal":"🤵🏽","name":"man in tuxedo tone 3","shortname":":man_in_tuxedo_tone3:","category":"people","emoji_order":"480","aliases":[":tuxedo_tone3:"],"aliases_ascii":[],"keywords":[]},"man_in_tuxedo_tone4":{"unicode":"1f935-1f3fe","unicode_alt":"","code_decimal":"🤵🏾","name":"man in tuxedo tone 4","shortname":":man_in_tuxedo_tone4:","category":"people","emoji_order":"481","aliases":[":tuxedo_tone4:"],"aliases_ascii":[],"keywords":[]},"man_in_tuxedo_tone5":{"unicode":"1f935-1f3ff","unicode_alt":"","code_decimal":"🤵🏿","name":"man in tuxedo tone 5","shortname":":man_in_tuxedo_tone5:","category":"people","emoji_order":"482","aliases":[":tuxedo_tone5:"],"aliases_ascii":[],"keywords":[]},"pregnant_woman":{"unicode":"1f930","unicode_alt":"","code_decimal":"🤰","name":"pregnant woman","shortname":":pregnant_woman:","category":"people","emoji_order":"483","aliases":[":expecting_woman:"],"aliases_ascii":[],"keywords":[]},"pregnant_woman_tone1":{"unicode":"1f930-1f3fb","unicode_alt":"","code_decimal":"🤰🏻","name":"pregnant woman tone 1","shortname":":pregnant_woman_tone1:","category":"people","emoji_order":"484","aliases":[":expecting_woman_tone1:"],"aliases_ascii":[],"keywords":[]},"pregnant_woman_tone2":{"unicode":"1f930-1f3fc","unicode_alt":"","code_decimal":"🤰🏼","name":"pregnant woman tone 2","shortname":":pregnant_woman_tone2:","category":"people","emoji_order":"485","aliases":[":expecting_woman_tone2:"],"aliases_ascii":[],"keywords":[]},"pregnant_woman_tone3":{"unicode":"1f930-1f3fd","unicode_alt":"","code_decimal":"🤰🏽","name":"pregnant woman tone 3","shortname":":pregnant_woman_tone3:","category":"people","emoji_order":"486","aliases":[":expecting_woman_tone3:"],"aliases_ascii":[],"keywords":[]},"pregnant_woman_tone4":{"unicode":"1f930-1f3fe","unicode_alt":"","code_decimal":"🤰🏾","name":"pregnant woman tone 4","shortname":":pregnant_woman_tone4:","category":"people","emoji_order":"487","aliases":[":expecting_woman_tone4:"],"aliases_ascii":[],"keywords":[]},"pregnant_woman_tone5":{"unicode":"1f930-1f3ff","unicode_alt":"","code_decimal":"🤰🏿","name":"pregnant woman tone 5","shortname":":pregnant_woman_tone5:","category":"people","emoji_order":"488","aliases":[":expecting_woman_tone5:"],"aliases_ascii":[],"keywords":[]},"man_with_gua_pi_mao":{"unicode":"1f472","unicode_alt":"","code_decimal":"👲","name":"man with gua pi mao","shortname":":man_with_gua_pi_mao:","category":"people","emoji_order":"489","aliases":[],"aliases_ascii":[],"keywords":["people","hat","men","diversity"]},"man_with_gua_pi_mao_tone1":{"unicode":"1f472-1f3fb","unicode_alt":"","code_decimal":"👲🏻","name":"man with gua pi mao tone 1","shortname":":man_with_gua_pi_mao_tone1:","category":"people","emoji_order":"490","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_gua_pi_mao_tone2":{"unicode":"1f472-1f3fc","unicode_alt":"","code_decimal":"👲🏼","name":"man with gua pi mao tone 2","shortname":":man_with_gua_pi_mao_tone2:","category":"people","emoji_order":"491","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_gua_pi_mao_tone3":{"unicode":"1f472-1f3fd","unicode_alt":"","code_decimal":"👲🏽","name":"man with gua pi mao tone 3","shortname":":man_with_gua_pi_mao_tone3:","category":"people","emoji_order":"492","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_gua_pi_mao_tone4":{"unicode":"1f472-1f3fe","unicode_alt":"","code_decimal":"👲🏾","name":"man with gua pi mao tone 4","shortname":":man_with_gua_pi_mao_tone4:","category":"people","emoji_order":"493","aliases":[],"aliases_ascii":[],"keywords":[]},"man_with_gua_pi_mao_tone5":{"unicode":"1f472-1f3ff","unicode_alt":"","code_decimal":"👲🏿","name":"man with gua pi mao tone 5","shortname":":man_with_gua_pi_mao_tone5:","category":"people","emoji_order":"494","aliases":[],"aliases_ascii":[],"keywords":[]},"person_frowning":{"unicode":"1f64d","unicode_alt":"","code_decimal":"🙍","name":"person frowning","shortname":":person_frowning:","category":"people","emoji_order":"495","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity"]},"person_frowning_tone1":{"unicode":"1f64d-1f3fb","unicode_alt":"","code_decimal":"🙍🏻","name":"person frowning tone 1","shortname":":person_frowning_tone1:","category":"people","emoji_order":"496","aliases":[],"aliases_ascii":[],"keywords":[]},"person_frowning_tone2":{"unicode":"1f64d-1f3fc","unicode_alt":"","code_decimal":"🙍🏼","name":"person frowning tone 2","shortname":":person_frowning_tone2:","category":"people","emoji_order":"497","aliases":[],"aliases_ascii":[],"keywords":[]},"person_frowning_tone3":{"unicode":"1f64d-1f3fd","unicode_alt":"","code_decimal":"🙍🏽","name":"person frowning tone 3","shortname":":person_frowning_tone3:","category":"people","emoji_order":"498","aliases":[],"aliases_ascii":[],"keywords":[]},"person_frowning_tone4":{"unicode":"1f64d-1f3fe","unicode_alt":"","code_decimal":"🙍🏾","name":"person frowning tone 4","shortname":":person_frowning_tone4:","category":"people","emoji_order":"499","aliases":[],"aliases_ascii":[],"keywords":[]},"person_frowning_tone5":{"unicode":"1f64d-1f3ff","unicode_alt":"","code_decimal":"🙍🏿","name":"person frowning tone 5","shortname":":person_frowning_tone5:","category":"people","emoji_order":"500","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_pouting_face":{"unicode":"1f64e","unicode_alt":"","code_decimal":"🙎","name":"person with pouting face","shortname":":person_with_pouting_face:","category":"people","emoji_order":"513","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity"]},"person_with_pouting_face_tone1":{"unicode":"1f64e-1f3fb","unicode_alt":"","code_decimal":"🙎🏻","name":"person with pouting face tone1","shortname":":person_with_pouting_face_tone1:","category":"people","emoji_order":"514","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_pouting_face_tone2":{"unicode":"1f64e-1f3fc","unicode_alt":"","code_decimal":"🙎🏼","name":"person with pouting face tone2","shortname":":person_with_pouting_face_tone2:","category":"people","emoji_order":"515","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_pouting_face_tone3":{"unicode":"1f64e-1f3fd","unicode_alt":"","code_decimal":"🙎🏽","name":"person with pouting face tone3","shortname":":person_with_pouting_face_tone3:","category":"people","emoji_order":"516","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_pouting_face_tone4":{"unicode":"1f64e-1f3fe","unicode_alt":"","code_decimal":"🙎🏾","name":"person with pouting face tone4","shortname":":person_with_pouting_face_tone4:","category":"people","emoji_order":"517","aliases":[],"aliases_ascii":[],"keywords":[]},"person_with_pouting_face_tone5":{"unicode":"1f64e-1f3ff","unicode_alt":"","code_decimal":"🙎🏿","name":"person with pouting face tone5","shortname":":person_with_pouting_face_tone5:","category":"people","emoji_order":"518","aliases":[],"aliases_ascii":[],"keywords":[]},"no_good":{"unicode":"1f645","unicode_alt":"","code_decimal":"🙅","name":"face with no good gesture","shortname":":no_good:","category":"people","emoji_order":"531","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity","girls night"]},"no_good_tone1":{"unicode":"1f645-1f3fb","unicode_alt":"","code_decimal":"🙅🏻","name":"face with no good gesture tone 1","shortname":":no_good_tone1:","category":"people","emoji_order":"532","aliases":[],"aliases_ascii":[],"keywords":[]},"no_good_tone2":{"unicode":"1f645-1f3fc","unicode_alt":"","code_decimal":"🙅🏼","name":"face with no good gesture tone 2","shortname":":no_good_tone2:","category":"people","emoji_order":"533","aliases":[],"aliases_ascii":[],"keywords":[]},"no_good_tone3":{"unicode":"1f645-1f3fd","unicode_alt":"","code_decimal":"🙅🏽","name":"face with no good gesture tone 3","shortname":":no_good_tone3:","category":"people","emoji_order":"534","aliases":[],"aliases_ascii":[],"keywords":[]},"no_good_tone4":{"unicode":"1f645-1f3fe","unicode_alt":"","code_decimal":"🙅🏾","name":"face with no good gesture tone 4","shortname":":no_good_tone4:","category":"people","emoji_order":"535","aliases":[],"aliases_ascii":[],"keywords":[]},"no_good_tone5":{"unicode":"1f645-1f3ff","unicode_alt":"","code_decimal":"🙅🏿","name":"face with no good gesture tone 5","shortname":":no_good_tone5:","category":"people","emoji_order":"536","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_woman":{"unicode":"1f646","unicode_alt":"","code_decimal":"🙆","name":"face with ok gesture","shortname":":ok_woman:","category":"people","emoji_order":"549","aliases":[],"aliases_ascii":["*\\0\/*","\\0\/","*\\O\/*","\\O\/"],"keywords":["people","women","diversity"]},"ok_woman_tone1":{"unicode":"1f646-1f3fb","unicode_alt":"","code_decimal":"🙆🏻","name":"face with ok gesture tone1","shortname":":ok_woman_tone1:","category":"people","emoji_order":"550","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_woman_tone2":{"unicode":"1f646-1f3fc","unicode_alt":"","code_decimal":"🙆🏼","name":"face with ok gesture tone2","shortname":":ok_woman_tone2:","category":"people","emoji_order":"551","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_woman_tone3":{"unicode":"1f646-1f3fd","unicode_alt":"","code_decimal":"🙆🏽","name":"face with ok gesture tone3","shortname":":ok_woman_tone3:","category":"people","emoji_order":"552","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_woman_tone4":{"unicode":"1f646-1f3fe","unicode_alt":"","code_decimal":"🙆🏾","name":"face with ok gesture tone4","shortname":":ok_woman_tone4:","category":"people","emoji_order":"553","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_woman_tone5":{"unicode":"1f646-1f3ff","unicode_alt":"","code_decimal":"🙆🏿","name":"face with ok gesture tone5","shortname":":ok_woman_tone5:","category":"people","emoji_order":"554","aliases":[],"aliases_ascii":[],"keywords":[]},"information_desk_person":{"unicode":"1f481","unicode_alt":"","code_decimal":"💁","name":"information desk person","shortname":":information_desk_person:","category":"people","emoji_order":"567","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity"]},"information_desk_person_tone1":{"unicode":"1f481-1f3fb","unicode_alt":"","code_decimal":"💁🏻","name":"information desk person tone 1","shortname":":information_desk_person_tone1:","category":"people","emoji_order":"568","aliases":[],"aliases_ascii":[],"keywords":[]},"information_desk_person_tone2":{"unicode":"1f481-1f3fc","unicode_alt":"","code_decimal":"💁🏼","name":"information desk person tone 2","shortname":":information_desk_person_tone2:","category":"people","emoji_order":"569","aliases":[],"aliases_ascii":[],"keywords":[]},"information_desk_person_tone3":{"unicode":"1f481-1f3fd","unicode_alt":"","code_decimal":"💁🏽","name":"information desk person tone 3","shortname":":information_desk_person_tone3:","category":"people","emoji_order":"570","aliases":[],"aliases_ascii":[],"keywords":[]},"information_desk_person_tone4":{"unicode":"1f481-1f3fe","unicode_alt":"","code_decimal":"💁🏾","name":"information desk person tone 4","shortname":":information_desk_person_tone4:","category":"people","emoji_order":"571","aliases":[],"aliases_ascii":[],"keywords":[]},"information_desk_person_tone5":{"unicode":"1f481-1f3ff","unicode_alt":"","code_decimal":"💁🏿","name":"information desk person tone 5","shortname":":information_desk_person_tone5:","category":"people","emoji_order":"572","aliases":[],"aliases_ascii":[],"keywords":[]},"raising_hand":{"unicode":"1f64b","unicode_alt":"","code_decimal":"🙋","name":"happy person raising one hand","shortname":":raising_hand:","category":"people","emoji_order":"585","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity"]},"raising_hand_tone1":{"unicode":"1f64b-1f3fb","unicode_alt":"","code_decimal":"🙋🏻","name":"happy person raising one hand tone1","shortname":":raising_hand_tone1:","category":"people","emoji_order":"586","aliases":[],"aliases_ascii":[],"keywords":[]},"raising_hand_tone2":{"unicode":"1f64b-1f3fc","unicode_alt":"","code_decimal":"🙋🏼","name":"happy person raising one hand tone2","shortname":":raising_hand_tone2:","category":"people","emoji_order":"587","aliases":[],"aliases_ascii":[],"keywords":[]},"raising_hand_tone3":{"unicode":"1f64b-1f3fd","unicode_alt":"","code_decimal":"🙋🏽","name":"happy person raising one hand tone3","shortname":":raising_hand_tone3:","category":"people","emoji_order":"588","aliases":[],"aliases_ascii":[],"keywords":[]},"raising_hand_tone4":{"unicode":"1f64b-1f3fe","unicode_alt":"","code_decimal":"🙋🏾","name":"happy person raising one hand tone4","shortname":":raising_hand_tone4:","category":"people","emoji_order":"589","aliases":[],"aliases_ascii":[],"keywords":[]},"raising_hand_tone5":{"unicode":"1f64b-1f3ff","unicode_alt":"","code_decimal":"🙋🏿","name":"happy person raising one hand tone5","shortname":":raising_hand_tone5:","category":"people","emoji_order":"590","aliases":[],"aliases_ascii":[],"keywords":[]},"bow":{"unicode":"1f647","unicode_alt":"","code_decimal":"🙇","name":"person bowing deeply","shortname":":bow:","category":"people","emoji_order":"603","aliases":[],"aliases_ascii":[],"keywords":["people","pray","diversity"]},"bow_tone1":{"unicode":"1f647-1f3fb","unicode_alt":"","code_decimal":"🙇🏻","name":"person bowing deeply tone 1","shortname":":bow_tone1:","category":"people","emoji_order":"604","aliases":[],"aliases_ascii":[],"keywords":[]},"bow_tone2":{"unicode":"1f647-1f3fc","unicode_alt":"","code_decimal":"🙇🏼","name":"person bowing deeply tone 2","shortname":":bow_tone2:","category":"people","emoji_order":"605","aliases":[],"aliases_ascii":[],"keywords":[]},"bow_tone3":{"unicode":"1f647-1f3fd","unicode_alt":"","code_decimal":"🙇🏽","name":"person bowing deeply tone 3","shortname":":bow_tone3:","category":"people","emoji_order":"606","aliases":[],"aliases_ascii":[],"keywords":[]},"bow_tone4":{"unicode":"1f647-1f3fe","unicode_alt":"","code_decimal":"🙇🏾","name":"person bowing deeply tone 4","shortname":":bow_tone4:","category":"people","emoji_order":"607","aliases":[],"aliases_ascii":[],"keywords":[]},"bow_tone5":{"unicode":"1f647-1f3ff","unicode_alt":"","code_decimal":"🙇🏿","name":"person bowing deeply tone 5","shortname":":bow_tone5:","category":"people","emoji_order":"608","aliases":[],"aliases_ascii":[],"keywords":[]},"face_palm":{"unicode":"1f926","unicode_alt":"","code_decimal":"🤦","name":"face palm","shortname":":face_palm:","category":"people","emoji_order":"621","aliases":[":facepalm:"],"aliases_ascii":[],"keywords":[]},"face_palm_tone1":{"unicode":"1f926-1f3fb","unicode_alt":"","code_decimal":"🤦🏻","name":"face palm tone 1","shortname":":face_palm_tone1:","category":"people","emoji_order":"622","aliases":[":facepalm_tone1:"],"aliases_ascii":[],"keywords":[]},"face_palm_tone2":{"unicode":"1f926-1f3fc","unicode_alt":"","code_decimal":"🤦🏼","name":"face palm tone 2","shortname":":face_palm_tone2:","category":"people","emoji_order":"623","aliases":[":facepalm_tone2:"],"aliases_ascii":[],"keywords":[]},"face_palm_tone3":{"unicode":"1f926-1f3fd","unicode_alt":"","code_decimal":"🤦🏽","name":"face palm tone 3","shortname":":face_palm_tone3:","category":"people","emoji_order":"624","aliases":[":facepalm_tone3:"],"aliases_ascii":[],"keywords":[]},"face_palm_tone4":{"unicode":"1f926-1f3fe","unicode_alt":"","code_decimal":"🤦🏾","name":"face palm tone 4","shortname":":face_palm_tone4:","category":"people","emoji_order":"625","aliases":[":facepalm_tone4:"],"aliases_ascii":[],"keywords":[]},"face_palm_tone5":{"unicode":"1f926-1f3ff","unicode_alt":"","code_decimal":"🤦🏿","name":"face palm tone 5","shortname":":face_palm_tone5:","category":"people","emoji_order":"626","aliases":[":facepalm_tone5:"],"aliases_ascii":[],"keywords":[]},"shrug":{"unicode":"1f937","unicode_alt":"","code_decimal":"🤷","name":"shrug","shortname":":shrug:","category":"people","emoji_order":"639","aliases":[],"aliases_ascii":[],"keywords":[]},"shrug_tone1":{"unicode":"1f937-1f3fb","unicode_alt":"","code_decimal":"🤷🏻","name":"shrug tone 1","shortname":":shrug_tone1:","category":"people","emoji_order":"640","aliases":[],"aliases_ascii":[],"keywords":[]},"shrug_tone2":{"unicode":"1f937-1f3fc","unicode_alt":"","code_decimal":"🤷🏼","name":"shrug tone 2","shortname":":shrug_tone2:","category":"people","emoji_order":"641","aliases":[],"aliases_ascii":[],"keywords":[]},"shrug_tone3":{"unicode":"1f937-1f3fd","unicode_alt":"","code_decimal":"🤷🏽","name":"shrug tone 3","shortname":":shrug_tone3:","category":"people","emoji_order":"642","aliases":[],"aliases_ascii":[],"keywords":[]},"shrug_tone4":{"unicode":"1f937-1f3fe","unicode_alt":"","code_decimal":"🤷🏾","name":"shrug tone 4","shortname":":shrug_tone4:","category":"people","emoji_order":"643","aliases":[],"aliases_ascii":[],"keywords":[]},"shrug_tone5":{"unicode":"1f937-1f3ff","unicode_alt":"","code_decimal":"🤷🏿","name":"shrug tone 5","shortname":":shrug_tone5:","category":"people","emoji_order":"644","aliases":[],"aliases_ascii":[],"keywords":[]},"massage":{"unicode":"1f486","unicode_alt":"","code_decimal":"💆","name":"face massage","shortname":":massage:","category":"people","emoji_order":"657","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity"]},"massage_tone1":{"unicode":"1f486-1f3fb","unicode_alt":"","code_decimal":"💆🏻","name":"face massage tone 1","shortname":":massage_tone1:","category":"people","emoji_order":"658","aliases":[],"aliases_ascii":[],"keywords":[]},"massage_tone2":{"unicode":"1f486-1f3fc","unicode_alt":"","code_decimal":"💆🏼","name":"face massage tone 2","shortname":":massage_tone2:","category":"people","emoji_order":"659","aliases":[],"aliases_ascii":[],"keywords":[]},"massage_tone3":{"unicode":"1f486-1f3fd","unicode_alt":"","code_decimal":"💆🏽","name":"face massage tone 3","shortname":":massage_tone3:","category":"people","emoji_order":"660","aliases":[],"aliases_ascii":[],"keywords":[]},"massage_tone4":{"unicode":"1f486-1f3fe","unicode_alt":"","code_decimal":"💆🏾","name":"face massage tone 4","shortname":":massage_tone4:","category":"people","emoji_order":"661","aliases":[],"aliases_ascii":[],"keywords":[]},"massage_tone5":{"unicode":"1f486-1f3ff","unicode_alt":"","code_decimal":"💆🏿","name":"face massage tone 5","shortname":":massage_tone5:","category":"people","emoji_order":"662","aliases":[],"aliases_ascii":[],"keywords":[]},"haircut":{"unicode":"1f487","unicode_alt":"","code_decimal":"💇","name":"haircut","shortname":":haircut:","category":"people","emoji_order":"675","aliases":[],"aliases_ascii":[],"keywords":["people","women","diversity"]},"haircut_tone1":{"unicode":"1f487-1f3fb","unicode_alt":"","code_decimal":"💇🏻","name":"haircut tone 1","shortname":":haircut_tone1:","category":"people","emoji_order":"676","aliases":[],"aliases_ascii":[],"keywords":[]},"haircut_tone2":{"unicode":"1f487-1f3fc","unicode_alt":"","code_decimal":"💇🏼","name":"haircut tone 2","shortname":":haircut_tone2:","category":"people","emoji_order":"677","aliases":[],"aliases_ascii":[],"keywords":[]},"haircut_tone3":{"unicode":"1f487-1f3fd","unicode_alt":"","code_decimal":"💇🏽","name":"haircut tone 3","shortname":":haircut_tone3:","category":"people","emoji_order":"678","aliases":[],"aliases_ascii":[],"keywords":[]},"haircut_tone4":{"unicode":"1f487-1f3fe","unicode_alt":"","code_decimal":"💇🏾","name":"haircut tone 4","shortname":":haircut_tone4:","category":"people","emoji_order":"679","aliases":[],"aliases_ascii":[],"keywords":[]},"haircut_tone5":{"unicode":"1f487-1f3ff","unicode_alt":"","code_decimal":"💇🏿","name":"haircut tone 5","shortname":":haircut_tone5:","category":"people","emoji_order":"680","aliases":[],"aliases_ascii":[],"keywords":[]},"walking":{"unicode":"1f6b6","unicode_alt":"","code_decimal":"🚶","name":"pedestrian","shortname":":walking:","category":"people","emoji_order":"693","aliases":[],"aliases_ascii":[],"keywords":["people","men","diversity"]},"walking_tone1":{"unicode":"1f6b6-1f3fb","unicode_alt":"","code_decimal":"🚶🏻","name":"pedestrian tone 1","shortname":":walking_tone1:","category":"people","emoji_order":"694","aliases":[],"aliases_ascii":[],"keywords":[]},"walking_tone2":{"unicode":"1f6b6-1f3fc","unicode_alt":"","code_decimal":"🚶🏼","name":"pedestrian tone 2","shortname":":walking_tone2:","category":"people","emoji_order":"695","aliases":[],"aliases_ascii":[],"keywords":[]},"walking_tone3":{"unicode":"1f6b6-1f3fd","unicode_alt":"","code_decimal":"🚶🏽","name":"pedestrian tone 3","shortname":":walking_tone3:","category":"people","emoji_order":"696","aliases":[],"aliases_ascii":[],"keywords":[]},"walking_tone4":{"unicode":"1f6b6-1f3fe","unicode_alt":"","code_decimal":"🚶🏾","name":"pedestrian tone 4","shortname":":walking_tone4:","category":"people","emoji_order":"697","aliases":[],"aliases_ascii":[],"keywords":[]},"walking_tone5":{"unicode":"1f6b6-1f3ff","unicode_alt":"","code_decimal":"🚶🏿","name":"pedestrian tone 5","shortname":":walking_tone5:","category":"people","emoji_order":"698","aliases":[],"aliases_ascii":[],"keywords":[]},"runner":{"unicode":"1f3c3","unicode_alt":"","code_decimal":"🏃","name":"runner","shortname":":runner:","category":"people","emoji_order":"711","aliases":[],"aliases_ascii":[],"keywords":["people","men","diversity","boys night","run"]},"runner_tone1":{"unicode":"1f3c3-1f3fb","unicode_alt":"","code_decimal":"🏃🏻","name":"runner tone 1","shortname":":runner_tone1:","category":"people","emoji_order":"712","aliases":[],"aliases_ascii":[],"keywords":[]},"runner_tone2":{"unicode":"1f3c3-1f3fc","unicode_alt":"","code_decimal":"🏃🏼","name":"runner tone 2","shortname":":runner_tone2:","category":"people","emoji_order":"713","aliases":[],"aliases_ascii":[],"keywords":[]},"runner_tone3":{"unicode":"1f3c3-1f3fd","unicode_alt":"","code_decimal":"🏃🏽","name":"runner tone 3","shortname":":runner_tone3:","category":"people","emoji_order":"714","aliases":[],"aliases_ascii":[],"keywords":[]},"runner_tone4":{"unicode":"1f3c3-1f3fe","unicode_alt":"","code_decimal":"🏃🏾","name":"runner tone 4","shortname":":runner_tone4:","category":"people","emoji_order":"715","aliases":[],"aliases_ascii":[],"keywords":[]},"runner_tone5":{"unicode":"1f3c3-1f3ff","unicode_alt":"","code_decimal":"🏃🏿","name":"runner tone 5","shortname":":runner_tone5:","category":"people","emoji_order":"716","aliases":[],"aliases_ascii":[],"keywords":[]},"dancer":{"unicode":"1f483","unicode_alt":"","code_decimal":"💃","name":"dancer","shortname":":dancer:","category":"people","emoji_order":"729","aliases":[],"aliases_ascii":[],"keywords":["people","women","sexy","diversity","girls night","dance"]},"dancer_tone1":{"unicode":"1f483-1f3fb","unicode_alt":"","code_decimal":"💃🏻","name":"dancer tone 1","shortname":":dancer_tone1:","category":"people","emoji_order":"730","aliases":[],"aliases_ascii":[],"keywords":[]},"dancer_tone2":{"unicode":"1f483-1f3fc","unicode_alt":"","code_decimal":"💃🏼","name":"dancer tone 2","shortname":":dancer_tone2:","category":"people","emoji_order":"731","aliases":[],"aliases_ascii":[],"keywords":[]},"dancer_tone3":{"unicode":"1f483-1f3fd","unicode_alt":"","code_decimal":"💃🏽","name":"dancer tone 3","shortname":":dancer_tone3:","category":"people","emoji_order":"732","aliases":[],"aliases_ascii":[],"keywords":[]},"dancer_tone4":{"unicode":"1f483-1f3fe","unicode_alt":"","code_decimal":"💃🏾","name":"dancer tone 4","shortname":":dancer_tone4:","category":"people","emoji_order":"733","aliases":[],"aliases_ascii":[],"keywords":[]},"dancer_tone5":{"unicode":"1f483-1f3ff","unicode_alt":"","code_decimal":"💃🏿","name":"dancer tone 5","shortname":":dancer_tone5:","category":"people","emoji_order":"734","aliases":[],"aliases_ascii":[],"keywords":[]},"man_dancing":{"unicode":"1f57a","unicode_alt":"","code_decimal":"🕺","name":"man dancing","shortname":":man_dancing:","category":"people","emoji_order":"735","aliases":[":male_dancer:"],"aliases_ascii":[],"keywords":[]},"man_dancing_tone1":{"unicode":"1f57a-1f3fb","unicode_alt":"","code_decimal":"🕺🏻","name":"man dancing tone 1","shortname":":man_dancing_tone1:","category":"people","emoji_order":"736","aliases":[":male_dancer_tone1:"],"aliases_ascii":[],"keywords":[]},"man_dancing_tone2":{"unicode":"1f57a-1f3fc","unicode_alt":"","code_decimal":"🕺🏼","name":"man dancing tone 2","shortname":":man_dancing_tone2:","category":"people","emoji_order":"737","aliases":[":male_dancer_tone2:"],"aliases_ascii":[],"keywords":[]},"man_dancing_tone3":{"unicode":"1f57a-1f3fd","unicode_alt":"","code_decimal":"🕺🏽","name":"man dancing tone 3","shortname":":man_dancing_tone3:","category":"people","emoji_order":"738","aliases":[":male_dancer_tone3:"],"aliases_ascii":[],"keywords":[]},"man_dancing_tone4":{"unicode":"1f57a-1f3fe","unicode_alt":"","code_decimal":"🕺🏾","name":"man dancing tone 4","shortname":":man_dancing_tone4:","category":"people","emoji_order":"739","aliases":[":male_dancer_tone4:"],"aliases_ascii":[],"keywords":[]},"man_dancing_tone5":{"unicode":"1f57a-1f3ff","unicode_alt":"","code_decimal":"🕺🏿","name":"man dancing tone 5","shortname":":man_dancing_tone5:","category":"people","emoji_order":"740","aliases":[":male_dancer_tone5:"],"aliases_ascii":[],"keywords":[]},"dancers":{"unicode":"1f46f","unicode_alt":"","code_decimal":"👯","name":"woman with bunny ears","shortname":":dancers:","category":"people","emoji_order":"741","aliases":[],"aliases_ascii":[],"keywords":["people","women","sexy","girls night","boys night","parties","dance"]},"levitate":{"unicode":"1f574","unicode_alt":"1f574-fe0f","code_decimal":"🕴","name":"man in business suit levitating","shortname":":levitate:","category":"activity","emoji_order":"759","aliases":[":man_in_business_suit_levitating:"],"aliases_ascii":[],"keywords":["men","job"]},"speaking_head":{"unicode":"1f5e3","unicode_alt":"1f5e3-fe0f","code_decimal":"🗣","name":"speaking head in silhouette","shortname":":speaking_head:","category":"people","emoji_order":"765","aliases":[":speaking_head_in_silhouette:"],"aliases_ascii":[],"keywords":["people","talk"]},"bust_in_silhouette":{"unicode":"1f464","unicode_alt":"","code_decimal":"👤","name":"bust in silhouette","shortname":":bust_in_silhouette:","category":"people","emoji_order":"766","aliases":[],"aliases_ascii":[],"keywords":["people"]},"busts_in_silhouette":{"unicode":"1f465","unicode_alt":"","code_decimal":"👥","name":"busts in silhouette","shortname":":busts_in_silhouette:","category":"people","emoji_order":"767","aliases":[],"aliases_ascii":[],"keywords":["people"]},"fencer":{"unicode":"1f93a","unicode_alt":"","code_decimal":"🤺","name":"fencer","shortname":":fencer:","category":"activity","emoji_order":"768","aliases":[":fencing:"],"aliases_ascii":[],"keywords":[]},"horse_racing":{"unicode":"1f3c7","unicode_alt":"","code_decimal":"🏇","name":"horse racing","shortname":":horse_racing:","category":"activity","emoji_order":"769","aliases":[],"aliases_ascii":[],"keywords":["men","sport","horse racing"]},"horse_racing_tone1":{"unicode":"1f3c7-1f3fb","unicode_alt":"","code_decimal":"🏇🏻","name":"horse racing tone 1","shortname":":horse_racing_tone1:","category":"activity","emoji_order":"770","aliases":[],"aliases_ascii":[],"keywords":[]},"horse_racing_tone2":{"unicode":"1f3c7-1f3fc","unicode_alt":"","code_decimal":"🏇🏼","name":"horse racing tone 2","shortname":":horse_racing_tone2:","category":"activity","emoji_order":"771","aliases":[],"aliases_ascii":[],"keywords":[]},"horse_racing_tone3":{"unicode":"1f3c7-1f3fd","unicode_alt":"","code_decimal":"🏇🏽","name":"horse racing tone 3","shortname":":horse_racing_tone3:","category":"activity","emoji_order":"772","aliases":[],"aliases_ascii":[],"keywords":[]},"horse_racing_tone4":{"unicode":"1f3c7-1f3fe","unicode_alt":"","code_decimal":"🏇🏾","name":"horse racing tone 4","shortname":":horse_racing_tone4:","category":"activity","emoji_order":"773","aliases":[],"aliases_ascii":[],"keywords":[]},"horse_racing_tone5":{"unicode":"1f3c7-1f3ff","unicode_alt":"","code_decimal":"🏇🏿","name":"horse racing tone 5","shortname":":horse_racing_tone5:","category":"activity","emoji_order":"774","aliases":[],"aliases_ascii":[],"keywords":[]},"skier":{"unicode":"26f7","unicode_alt":"26f7-fe0f","code_decimal":"⛷","name":"skier","shortname":":skier:","category":"activity","emoji_order":"775","aliases":[],"aliases_ascii":[],"keywords":["hat","vacation","cold","sport","skiing"]},"snowboarder":{"unicode":"1f3c2","unicode_alt":"","code_decimal":"🏂","name":"snowboarder","shortname":":snowboarder:","category":"activity","emoji_order":"776","aliases":[],"aliases_ascii":[],"keywords":["hat","vacation","cold","sport","snowboarding"]},"golfer":{"unicode":"1f3cc","unicode_alt":"1f3cc-fe0f","code_decimal":"🏌","name":"golfer","shortname":":golfer:","category":"activity","emoji_order":"782","aliases":[],"aliases_ascii":[],"keywords":["men","game","ball","vacation","sport","golf"]},"surfer":{"unicode":"1f3c4","unicode_alt":"","code_decimal":"🏄","name":"surfer","shortname":":surfer:","category":"activity","emoji_order":"800","aliases":[],"aliases_ascii":[],"keywords":["men","vacation","tropical","sport","diversity"]},"surfer_tone1":{"unicode":"1f3c4-1f3fb","unicode_alt":"","code_decimal":"🏄🏻","name":"surfer tone 1","shortname":":surfer_tone1:","category":"activity","emoji_order":"801","aliases":[],"aliases_ascii":[],"keywords":[]},"surfer_tone2":{"unicode":"1f3c4-1f3fc","unicode_alt":"","code_decimal":"🏄🏼","name":"surfer tone 2","shortname":":surfer_tone2:","category":"activity","emoji_order":"802","aliases":[],"aliases_ascii":[],"keywords":[]},"surfer_tone3":{"unicode":"1f3c4-1f3fd","unicode_alt":"","code_decimal":"🏄🏽","name":"surfer tone 3","shortname":":surfer_tone3:","category":"activity","emoji_order":"803","aliases":[],"aliases_ascii":[],"keywords":[]},"surfer_tone4":{"unicode":"1f3c4-1f3fe","unicode_alt":"","code_decimal":"🏄🏾","name":"surfer tone 4","shortname":":surfer_tone4:","category":"activity","emoji_order":"804","aliases":[],"aliases_ascii":[],"keywords":[]},"surfer_tone5":{"unicode":"1f3c4-1f3ff","unicode_alt":"","code_decimal":"🏄🏿","name":"surfer tone 5","shortname":":surfer_tone5:","category":"activity","emoji_order":"805","aliases":[],"aliases_ascii":[],"keywords":[]},"rowboat":{"unicode":"1f6a3","unicode_alt":"","code_decimal":"🚣","name":"rowboat","shortname":":rowboat:","category":"activity","emoji_order":"818","aliases":[],"aliases_ascii":[],"keywords":["men","workout","sport","rowing","diversity"]},"rowboat_tone1":{"unicode":"1f6a3-1f3fb","unicode_alt":"","code_decimal":"🚣🏻","name":"rowboat tone 1","shortname":":rowboat_tone1:","category":"activity","emoji_order":"819","aliases":[],"aliases_ascii":[],"keywords":[]},"rowboat_tone2":{"unicode":"1f6a3-1f3fc","unicode_alt":"","code_decimal":"🚣🏼","name":"rowboat tone 2","shortname":":rowboat_tone2:","category":"activity","emoji_order":"820","aliases":[],"aliases_ascii":[],"keywords":[]},"rowboat_tone3":{"unicode":"1f6a3-1f3fd","unicode_alt":"","code_decimal":"🚣🏽","name":"rowboat tone 3","shortname":":rowboat_tone3:","category":"activity","emoji_order":"821","aliases":[],"aliases_ascii":[],"keywords":[]},"rowboat_tone4":{"unicode":"1f6a3-1f3fe","unicode_alt":"","code_decimal":"🚣🏾","name":"rowboat tone 4","shortname":":rowboat_tone4:","category":"activity","emoji_order":"822","aliases":[],"aliases_ascii":[],"keywords":[]},"rowboat_tone5":{"unicode":"1f6a3-1f3ff","unicode_alt":"","code_decimal":"🚣🏿","name":"rowboat tone 5","shortname":":rowboat_tone5:","category":"activity","emoji_order":"823","aliases":[],"aliases_ascii":[],"keywords":[]},"swimmer":{"unicode":"1f3ca","unicode_alt":"","code_decimal":"🏊","name":"swimmer","shortname":":swimmer:","category":"activity","emoji_order":"836","aliases":[],"aliases_ascii":[],"keywords":["workout","sport","swim","diversity"]},"swimmer_tone1":{"unicode":"1f3ca-1f3fb","unicode_alt":"","code_decimal":"🏊🏻","name":"swimmer tone 1","shortname":":swimmer_tone1:","category":"activity","emoji_order":"837","aliases":[],"aliases_ascii":[],"keywords":[]},"swimmer_tone2":{"unicode":"1f3ca-1f3fc","unicode_alt":"","code_decimal":"🏊🏼","name":"swimmer tone 2","shortname":":swimmer_tone2:","category":"activity","emoji_order":"838","aliases":[],"aliases_ascii":[],"keywords":[]},"swimmer_tone3":{"unicode":"1f3ca-1f3fd","unicode_alt":"","code_decimal":"🏊🏽","name":"swimmer tone 3","shortname":":swimmer_tone3:","category":"activity","emoji_order":"839","aliases":[],"aliases_ascii":[],"keywords":[]},"swimmer_tone4":{"unicode":"1f3ca-1f3fe","unicode_alt":"","code_decimal":"🏊🏾","name":"swimmer tone 4","shortname":":swimmer_tone4:","category":"activity","emoji_order":"840","aliases":[],"aliases_ascii":[],"keywords":[]},"swimmer_tone5":{"unicode":"1f3ca-1f3ff","unicode_alt":"","code_decimal":"🏊🏿","name":"swimmer tone 5","shortname":":swimmer_tone5:","category":"activity","emoji_order":"841","aliases":[],"aliases_ascii":[],"keywords":[]},"basketball_player":{"unicode":"26f9","unicode_alt":"26f9-fe0f","code_decimal":"⛹","name":"person with ball","shortname":":basketball_player:","category":"activity","emoji_order":"854","aliases":[":person_with_ball:"],"aliases_ascii":[],"keywords":["men","game","ball","sport","basketball","diversity"]},"basketball_player_tone1":{"unicode":"26f9-1f3fb","unicode_alt":"","code_decimal":"⛹🏻","name":"person with ball tone 1","shortname":":basketball_player_tone1:","category":"activity","emoji_order":"855","aliases":[":person_with_ball_tone1:"],"aliases_ascii":[],"keywords":[]},"basketball_player_tone2":{"unicode":"26f9-1f3fc","unicode_alt":"","code_decimal":"⛹🏼","name":"person with ball tone 2","shortname":":basketball_player_tone2:","category":"activity","emoji_order":"856","aliases":[":person_with_ball_tone2:"],"aliases_ascii":[],"keywords":[]},"basketball_player_tone3":{"unicode":"26f9-1f3fd","unicode_alt":"","code_decimal":"⛹🏽","name":"person with ball tone 3","shortname":":basketball_player_tone3:","category":"activity","emoji_order":"857","aliases":[":person_with_ball_tone3:"],"aliases_ascii":[],"keywords":[]},"basketball_player_tone4":{"unicode":"26f9-1f3fe","unicode_alt":"","code_decimal":"⛹🏾","name":"person with ball tone 4","shortname":":basketball_player_tone4:","category":"activity","emoji_order":"858","aliases":[":person_with_ball_tone4:"],"aliases_ascii":[],"keywords":[]},"basketball_player_tone5":{"unicode":"26f9-1f3ff","unicode_alt":"","code_decimal":"⛹🏿","name":"person with ball tone 5","shortname":":basketball_player_tone5:","category":"activity","emoji_order":"859","aliases":[":person_with_ball_tone5:"],"aliases_ascii":[],"keywords":[]},"lifter":{"unicode":"1f3cb","unicode_alt":"1f3cb-fe0f","code_decimal":"🏋","name":"weight lifter","shortname":":lifter:","category":"activity","emoji_order":"872","aliases":[":weight_lifter:"],"aliases_ascii":[],"keywords":["men","workout","flex","sport","weight lifting","win","diversity"]},"lifter_tone1":{"unicode":"1f3cb-1f3fb","unicode_alt":"","code_decimal":"🏋🏻","name":"weight lifter tone 1","shortname":":lifter_tone1:","category":"activity","emoji_order":"873","aliases":[":weight_lifter_tone1:"],"aliases_ascii":[],"keywords":[]},"lifter_tone2":{"unicode":"1f3cb-1f3fc","unicode_alt":"","code_decimal":"🏋🏼","name":"weight lifter tone 2","shortname":":lifter_tone2:","category":"activity","emoji_order":"874","aliases":[":weight_lifter_tone2:"],"aliases_ascii":[],"keywords":[]},"lifter_tone3":{"unicode":"1f3cb-1f3fd","unicode_alt":"","code_decimal":"🏋🏽","name":"weight lifter tone 3","shortname":":lifter_tone3:","category":"activity","emoji_order":"875","aliases":[":weight_lifter_tone3:"],"aliases_ascii":[],"keywords":[]},"lifter_tone4":{"unicode":"1f3cb-1f3fe","unicode_alt":"","code_decimal":"🏋🏾","name":"weight lifter tone 4","shortname":":lifter_tone4:","category":"activity","emoji_order":"876","aliases":[":weight_lifter_tone4:"],"aliases_ascii":[],"keywords":[]},"lifter_tone5":{"unicode":"1f3cb-1f3ff","unicode_alt":"","code_decimal":"🏋🏿","name":"weight lifter tone 5","shortname":":lifter_tone5:","category":"activity","emoji_order":"877","aliases":[":weight_lifter_tone5:"],"aliases_ascii":[],"keywords":[]},"bicyclist":{"unicode":"1f6b4","unicode_alt":"","code_decimal":"🚴","name":"bicyclist","shortname":":bicyclist:","category":"activity","emoji_order":"890","aliases":[],"aliases_ascii":[],"keywords":["men","workout","sport","bike","diversity"]},"bicyclist_tone1":{"unicode":"1f6b4-1f3fb","unicode_alt":"","code_decimal":"🚴🏻","name":"bicyclist tone 1","shortname":":bicyclist_tone1:","category":"activity","emoji_order":"891","aliases":[],"aliases_ascii":[],"keywords":[]},"bicyclist_tone2":{"unicode":"1f6b4-1f3fc","unicode_alt":"","code_decimal":"🚴🏼","name":"bicyclist tone 2","shortname":":bicyclist_tone2:","category":"activity","emoji_order":"892","aliases":[],"aliases_ascii":[],"keywords":[]},"bicyclist_tone3":{"unicode":"1f6b4-1f3fd","unicode_alt":"","code_decimal":"🚴🏽","name":"bicyclist tone 3","shortname":":bicyclist_tone3:","category":"activity","emoji_order":"893","aliases":[],"aliases_ascii":[],"keywords":[]},"bicyclist_tone4":{"unicode":"1f6b4-1f3fe","unicode_alt":"","code_decimal":"🚴🏾","name":"bicyclist tone 4","shortname":":bicyclist_tone4:","category":"activity","emoji_order":"894","aliases":[],"aliases_ascii":[],"keywords":[]},"bicyclist_tone5":{"unicode":"1f6b4-1f3ff","unicode_alt":"","code_decimal":"🚴🏿","name":"bicyclist tone 5","shortname":":bicyclist_tone5:","category":"activity","emoji_order":"895","aliases":[],"aliases_ascii":[],"keywords":[]},"mountain_bicyclist":{"unicode":"1f6b5","unicode_alt":"","code_decimal":"🚵","name":"mountain bicyclist","shortname":":mountain_bicyclist:","category":"activity","emoji_order":"908","aliases":[],"aliases_ascii":[],"keywords":["men","sport","bike","diversity"]},"mountain_bicyclist_tone1":{"unicode":"1f6b5-1f3fb","unicode_alt":"","code_decimal":"🚵🏻","name":"mountain bicyclist tone 1","shortname":":mountain_bicyclist_tone1:","category":"activity","emoji_order":"909","aliases":[],"aliases_ascii":[],"keywords":[]},"mountain_bicyclist_tone2":{"unicode":"1f6b5-1f3fc","unicode_alt":"","code_decimal":"🚵🏼","name":"mountain bicyclist tone 2","shortname":":mountain_bicyclist_tone2:","category":"activity","emoji_order":"910","aliases":[],"aliases_ascii":[],"keywords":[]},"mountain_bicyclist_tone3":{"unicode":"1f6b5-1f3fd","unicode_alt":"","code_decimal":"🚵🏽","name":"mountain bicyclist tone 3","shortname":":mountain_bicyclist_tone3:","category":"activity","emoji_order":"911","aliases":[],"aliases_ascii":[],"keywords":[]},"mountain_bicyclist_tone4":{"unicode":"1f6b5-1f3fe","unicode_alt":"","code_decimal":"🚵🏾","name":"mountain bicyclist tone 4","shortname":":mountain_bicyclist_tone4:","category":"activity","emoji_order":"912","aliases":[],"aliases_ascii":[],"keywords":[]},"mountain_bicyclist_tone5":{"unicode":"1f6b5-1f3ff","unicode_alt":"","code_decimal":"🚵🏿","name":"mountain bicyclist tone 5","shortname":":mountain_bicyclist_tone5:","category":"activity","emoji_order":"913","aliases":[],"aliases_ascii":[],"keywords":[]},"race_car":{"unicode":"1f3ce","unicode_alt":"1f3ce-fe0f","code_decimal":"🏎","name":"racing car","shortname":":race_car:","category":"travel","emoji_order":"926","aliases":[":racing_car:"],"aliases_ascii":[],"keywords":["transportation","car"]},"motorcycle":{"unicode":"1f3cd","unicode_alt":"1f3cd-fe0f","code_decimal":"🏍","name":"racing motorcycle","shortname":":motorcycle:","category":"travel","emoji_order":"927","aliases":[":racing_motorcycle:"],"aliases_ascii":[],"keywords":["transportation","travel","bike"]},"cartwheel":{"unicode":"1f938","unicode_alt":"","code_decimal":"🤸","name":"person doing cartwheel","shortname":":cartwheel:","category":"activity","emoji_order":"928","aliases":[":person_doing_cartwheel:"],"aliases_ascii":[],"keywords":[]},"cartwheel_tone1":{"unicode":"1f938-1f3fb","unicode_alt":"","code_decimal":"🤸🏻","name":"person doing cartwheel tone 1","shortname":":cartwheel_tone1:","category":"activity","emoji_order":"929","aliases":[":person_doing_cartwheel_tone1:"],"aliases_ascii":[],"keywords":[]},"cartwheel_tone2":{"unicode":"1f938-1f3fc","unicode_alt":"","code_decimal":"🤸🏼","name":"person doing cartwheel tone 2","shortname":":cartwheel_tone2:","category":"activity","emoji_order":"930","aliases":[":person_doing_cartwheel_tone2:"],"aliases_ascii":[],"keywords":[]},"cartwheel_tone3":{"unicode":"1f938-1f3fd","unicode_alt":"","code_decimal":"🤸🏽","name":"person doing cartwheel tone 3","shortname":":cartwheel_tone3:","category":"activity","emoji_order":"931","aliases":[":person_doing_cartwheel_tone3:"],"aliases_ascii":[],"keywords":[]},"cartwheel_tone4":{"unicode":"1f938-1f3fe","unicode_alt":"","code_decimal":"🤸🏾","name":"person doing cartwheel tone 4","shortname":":cartwheel_tone4:","category":"activity","emoji_order":"932","aliases":[":person_doing_cartwheel_tone4:"],"aliases_ascii":[],"keywords":[]},"cartwheel_tone5":{"unicode":"1f938-1f3ff","unicode_alt":"","code_decimal":"🤸🏿","name":"person doing cartwheel tone 5","shortname":":cartwheel_tone5:","category":"activity","emoji_order":"933","aliases":[":person_doing_cartwheel_tone5:"],"aliases_ascii":[],"keywords":[]},"wrestlers":{"unicode":"1f93c","unicode_alt":"","code_decimal":"🤼","name":"wrestlers","shortname":":wrestlers:","category":"activity","emoji_order":"946","aliases":[":wrestling:"],"aliases_ascii":[],"keywords":[]},"wrestlers_tone1":{"unicode":"1f93c-1f3fb","unicode_alt":"","code_decimal":"🤼🏻","name":"wrestlers tone 1","shortname":":wrestlers_tone1:","category":"activity","emoji_order":"947","aliases":[":wrestling_tone1:"],"aliases_ascii":[],"keywords":[]},"wrestlers_tone2":{"unicode":"1f93c-1f3fc","unicode_alt":"","code_decimal":"🤼🏼","name":"wrestlers tone 2","shortname":":wrestlers_tone2:","category":"activity","emoji_order":"948","aliases":[":wrestling_tone2:"],"aliases_ascii":[],"keywords":[]},"wrestlers_tone3":{"unicode":"1f93c-1f3fd","unicode_alt":"","code_decimal":"🤼🏽","name":"wrestlers tone 3","shortname":":wrestlers_tone3:","category":"activity","emoji_order":"949","aliases":[":wrestling_tone3:"],"aliases_ascii":[],"keywords":[]},"wrestlers_tone4":{"unicode":"1f93c-1f3fe","unicode_alt":"","code_decimal":"🤼🏾","name":"wrestlers tone 4","shortname":":wrestlers_tone4:","category":"activity","emoji_order":"950","aliases":[":wrestling_tone4:"],"aliases_ascii":[],"keywords":[]},"wrestlers_tone5":{"unicode":"1f93c-1f3ff","unicode_alt":"","code_decimal":"🤼🏿","name":"wrestlers tone 5","shortname":":wrestlers_tone5:","category":"activity","emoji_order":"951","aliases":[":wrestling_tone5:"],"aliases_ascii":[],"keywords":[]},"water_polo":{"unicode":"1f93d","unicode_alt":"","code_decimal":"🤽","name":"water polo","shortname":":water_polo:","category":"activity","emoji_order":"964","aliases":[],"aliases_ascii":[],"keywords":[]},"water_polo_tone1":{"unicode":"1f93d-1f3fb","unicode_alt":"","code_decimal":"🤽🏻","name":"water polo tone 1","shortname":":water_polo_tone1:","category":"activity","emoji_order":"965","aliases":[],"aliases_ascii":[],"keywords":[]},"water_polo_tone2":{"unicode":"1f93d-1f3fc","unicode_alt":"","code_decimal":"🤽🏼","name":"water polo tone 2","shortname":":water_polo_tone2:","category":"activity","emoji_order":"966","aliases":[],"aliases_ascii":[],"keywords":[]},"water_polo_tone3":{"unicode":"1f93d-1f3fd","unicode_alt":"","code_decimal":"🤽🏽","name":"water polo tone 3","shortname":":water_polo_tone3:","category":"activity","emoji_order":"967","aliases":[],"aliases_ascii":[],"keywords":[]},"water_polo_tone4":{"unicode":"1f93d-1f3fe","unicode_alt":"","code_decimal":"🤽🏾","name":"water polo tone 4","shortname":":water_polo_tone4:","category":"activity","emoji_order":"968","aliases":[],"aliases_ascii":[],"keywords":[]},"water_polo_tone5":{"unicode":"1f93d-1f3ff","unicode_alt":"","code_decimal":"🤽🏿","name":"water polo tone 5","shortname":":water_polo_tone5:","category":"activity","emoji_order":"969","aliases":[],"aliases_ascii":[],"keywords":[]},"handball":{"unicode":"1f93e","unicode_alt":"","code_decimal":"🤾","name":"handball","shortname":":handball:","category":"activity","emoji_order":"982","aliases":[],"aliases_ascii":[],"keywords":[]},"handball_tone1":{"unicode":"1f93e-1f3fb","unicode_alt":"","code_decimal":"🤾🏻","name":"handball tone 1","shortname":":handball_tone1:","category":"activity","emoji_order":"983","aliases":[],"aliases_ascii":[],"keywords":[]},"handball_tone2":{"unicode":"1f93e-1f3fc","unicode_alt":"","code_decimal":"🤾🏼","name":"handball tone 2","shortname":":handball_tone2:","category":"activity","emoji_order":"984","aliases":[],"aliases_ascii":[],"keywords":[]},"handball_tone3":{"unicode":"1f93e-1f3fd","unicode_alt":"","code_decimal":"🤾🏽","name":"handball tone 3","shortname":":handball_tone3:","category":"activity","emoji_order":"985","aliases":[],"aliases_ascii":[],"keywords":[]},"handball_tone4":{"unicode":"1f93e-1f3fe","unicode_alt":"","code_decimal":"🤾🏾","name":"handball tone 4","shortname":":handball_tone4:","category":"activity","emoji_order":"986","aliases":[],"aliases_ascii":[],"keywords":[]},"handball_tone5":{"unicode":"1f93e-1f3ff","unicode_alt":"","code_decimal":"🤾🏿","name":"handball tone 5","shortname":":handball_tone5:","category":"activity","emoji_order":"987","aliases":[],"aliases_ascii":[],"keywords":[]},"juggling":{"unicode":"1f939","unicode_alt":"","code_decimal":"🤹","name":"juggling","shortname":":juggling:","category":"activity","emoji_order":"1000","aliases":[":juggler:"],"aliases_ascii":[],"keywords":[]},"juggling_tone1":{"unicode":"1f939-1f3fb","unicode_alt":"","code_decimal":"🤹🏻","name":"juggling tone 1","shortname":":juggling_tone1:","category":"activity","emoji_order":"1001","aliases":[":juggler_tone1:"],"aliases_ascii":[],"keywords":[]},"juggling_tone2":{"unicode":"1f939-1f3fc","unicode_alt":"","code_decimal":"🤹🏼","name":"juggling tone 2","shortname":":juggling_tone2:","category":"activity","emoji_order":"1002","aliases":[":juggler_tone2:"],"aliases_ascii":[],"keywords":[]},"juggling_tone3":{"unicode":"1f939-1f3fd","unicode_alt":"","code_decimal":"🤹🏽","name":"juggling tone 3","shortname":":juggling_tone3:","category":"activity","emoji_order":"1003","aliases":[":juggler_tone3:"],"aliases_ascii":[],"keywords":[]},"juggling_tone4":{"unicode":"1f939-1f3fe","unicode_alt":"","code_decimal":"🤹🏾","name":"juggling tone 4","shortname":":juggling_tone4:","category":"activity","emoji_order":"1004","aliases":[":juggler_tone4:"],"aliases_ascii":[],"keywords":[]},"juggling_tone5":{"unicode":"1f939-1f3ff","unicode_alt":"","code_decimal":"🤹🏿","name":"juggling tone 5","shortname":":juggling_tone5:","category":"activity","emoji_order":"1005","aliases":[":juggler_tone5:"],"aliases_ascii":[],"keywords":[]},"couple":{"unicode":"1f46b","unicode_alt":"","code_decimal":"👫","name":"man and woman holding hands","shortname":":couple:","category":"people","emoji_order":"1018","aliases":[],"aliases_ascii":[],"keywords":["people","sex","creationism"]},"two_men_holding_hands":{"unicode":"1f46c","unicode_alt":"","code_decimal":"👬","name":"two men holding hands","shortname":":two_men_holding_hands:","category":"people","emoji_order":"1024","aliases":[],"aliases_ascii":[],"keywords":["people","gay","men","sex","lgbt"]},"two_women_holding_hands":{"unicode":"1f46d","unicode_alt":"","code_decimal":"👭","name":"two women holding hands","shortname":":two_women_holding_hands:","category":"people","emoji_order":"1030","aliases":[],"aliases_ascii":[],"keywords":["people","women","sex","lgbt","lesbian","girls night"]},"couplekiss":{"unicode":"1f48f","unicode_alt":"","code_decimal":"💏","name":"kiss","shortname":":couplekiss:","category":"people","emoji_order":"1036","aliases":[],"aliases_ascii":[],"keywords":["people","love","sex"]},"kiss_mm":{"unicode":"1f468-2764-1f48b-1f468","unicode_alt":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","code_decimal":"👨❤💋👨","name":"kiss (man,man)","shortname":":kiss_mm:","category":"people","emoji_order":"1038","aliases":[":couplekiss_mm:"],"aliases_ascii":[],"keywords":["people","gay","men","love","sex","lgbt"]},"kiss_ww":{"unicode":"1f469-2764-1f48b-1f469","unicode_alt":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","code_decimal":"👩❤💋👩","name":"kiss (woman,woman)","shortname":":kiss_ww:","category":"people","emoji_order":"1039","aliases":[":couplekiss_ww:"],"aliases_ascii":[],"keywords":["people","women","love","sex","lgbt","lesbian"]},"couple_with_heart":{"unicode":"1f491","unicode_alt":"","code_decimal":"💑","name":"couple with heart","shortname":":couple_with_heart:","category":"people","emoji_order":"1040","aliases":[],"aliases_ascii":[],"keywords":["people","love","sex"]},"couple_mm":{"unicode":"1f468-2764-1f468","unicode_alt":"1f468-200d-2764-fe0f-200d-1f468","code_decimal":"👨❤👨","name":"couple (man,man)","shortname":":couple_mm:","category":"people","emoji_order":"1042","aliases":[":couple_with_heart_mm:"],"aliases_ascii":[],"keywords":["people","gay","men","love","sex","lgbt"]},"couple_ww":{"unicode":"1f469-2764-1f469","unicode_alt":"1f469-200d-2764-fe0f-200d-1f469","code_decimal":"👩❤👩","name":"couple (woman,woman)","shortname":":couple_ww:","category":"people","emoji_order":"1043","aliases":[":couple_with_heart_ww:"],"aliases_ascii":[],"keywords":["people","women","love","sex","lgbt"]},"family":{"unicode":"1f46a","unicode_alt":"","code_decimal":"👪","name":"family","shortname":":family:","category":"people","emoji_order":"1044","aliases":[],"aliases_ascii":[],"keywords":["people","family","baby"]},"family_mwg":{"unicode":"1f468-1f469-1f467","unicode_alt":"1f468-200d-1f469-200d-1f467","code_decimal":"👨👩👧","name":"family (man,woman,girl)","shortname":":family_mwg:","category":"people","emoji_order":"1051","aliases":[],"aliases_ascii":[],"keywords":["people","family","baby"]},"family_mwgb":{"unicode":"1f468-1f469-1f467-1f466","unicode_alt":"1f468-200d-1f469-200d-1f467-200d-1f466","code_decimal":"👨👩👧👦","name":"family (man,woman,girl,boy)","shortname":":family_mwgb:","category":"people","emoji_order":"1052","aliases":[],"aliases_ascii":[],"keywords":["people","family","baby"]},"family_mwbb":{"unicode":"1f468-1f469-1f466-1f466","unicode_alt":"1f468-200d-1f469-200d-1f466-200d-1f466","code_decimal":"👨👩👦👦","name":"family (man,woman,boy,boy)","shortname":":family_mwbb:","category":"people","emoji_order":"1053","aliases":[],"aliases_ascii":[],"keywords":["people","family","baby"]},"family_mwgg":{"unicode":"1f468-1f469-1f467-1f467","unicode_alt":"1f468-200d-1f469-200d-1f467-200d-1f467","code_decimal":"👨👩👧👧","name":"family (man,woman,girl,girl)","shortname":":family_mwgg:","category":"people","emoji_order":"1054","aliases":[],"aliases_ascii":[],"keywords":["people","family","baby"]},"family_mmb":{"unicode":"1f468-1f468-1f466","unicode_alt":"1f468-200d-1f468-200d-1f466","code_decimal":"👨👨👦","name":"family (man,man,boy)","shortname":":family_mmb:","category":"people","emoji_order":"1055","aliases":[],"aliases_ascii":[],"keywords":["people","gay","family","men","baby","lgbt"]},"family_mmg":{"unicode":"1f468-1f468-1f467","unicode_alt":"1f468-200d-1f468-200d-1f467","code_decimal":"👨👨👧","name":"family (man,man,girl)","shortname":":family_mmg:","category":"people","emoji_order":"1056","aliases":[],"aliases_ascii":[],"keywords":["people","gay","family","men","baby","lgbt"]},"family_mmgb":{"unicode":"1f468-1f468-1f467-1f466","unicode_alt":"1f468-200d-1f468-200d-1f467-200d-1f466","code_decimal":"👨👨👧👦","name":"family (man,man,girl,boy)","shortname":":family_mmgb:","category":"people","emoji_order":"1057","aliases":[],"aliases_ascii":[],"keywords":["people","gay","family","men","baby","lgbt"]},"family_mmbb":{"unicode":"1f468-1f468-1f466-1f466","unicode_alt":"1f468-200d-1f468-200d-1f466-200d-1f466","code_decimal":"👨👨👦👦","name":"family (man,man,boy,boy)","shortname":":family_mmbb:","category":"people","emoji_order":"1058","aliases":[],"aliases_ascii":[],"keywords":["people","gay","family","men","baby","lgbt"]},"family_mmgg":{"unicode":"1f468-1f468-1f467-1f467","unicode_alt":"1f468-200d-1f468-200d-1f467-200d-1f467","code_decimal":"👨👨👧👧","name":"family (man,man,girl,girl)","shortname":":family_mmgg:","category":"people","emoji_order":"1059","aliases":[],"aliases_ascii":[],"keywords":["people","gay","family","men","baby","lgbt"]},"family_wwb":{"unicode":"1f469-1f469-1f466","unicode_alt":"1f469-200d-1f469-200d-1f466","code_decimal":"👩👩👦","name":"family (woman,woman,boy)","shortname":":family_wwb:","category":"people","emoji_order":"1060","aliases":[],"aliases_ascii":[],"keywords":["people","family","women","baby","lgbt","lesbian"]},"family_wwg":{"unicode":"1f469-1f469-1f467","unicode_alt":"1f469-200d-1f469-200d-1f467","code_decimal":"👩👩👧","name":"family (woman,woman,girl)","shortname":":family_wwg:","category":"people","emoji_order":"1061","aliases":[],"aliases_ascii":[],"keywords":["people","family","women","baby","lgbt","lesbian"]},"family_wwgb":{"unicode":"1f469-1f469-1f467-1f466","unicode_alt":"1f469-200d-1f469-200d-1f467-200d-1f466","code_decimal":"👩👩👧👦","name":"family (woman,woman,girl,boy)","shortname":":family_wwgb:","category":"people","emoji_order":"1062","aliases":[],"aliases_ascii":[],"keywords":["people","family","women","baby","lgbt","lesbian"]},"family_wwbb":{"unicode":"1f469-1f469-1f466-1f466","unicode_alt":"1f469-200d-1f469-200d-1f466-200d-1f466","code_decimal":"👩👩👦👦","name":"family (woman,woman,boy,boy)","shortname":":family_wwbb:","category":"people","emoji_order":"1063","aliases":[],"aliases_ascii":[],"keywords":["people","family","women","baby","lgbt","lesbian"]},"family_wwgg":{"unicode":"1f469-1f469-1f467-1f467","unicode_alt":"1f469-200d-1f469-200d-1f467-200d-1f467","code_decimal":"👩👩👧👧","name":"family (woman,woman,girl,girl)","shortname":":family_wwgg:","category":"people","emoji_order":"1064","aliases":[],"aliases_ascii":[],"keywords":["people","family","women","baby","lgbt","lesbian"]},"tone1":{"unicode":"1f3fb","unicode_alt":"","code_decimal":"🏻","name":"emoji modifier Fitzpatrick type-1-2","shortname":":tone1:","category":"modifier","emoji_order":"1075","aliases":[],"aliases_ascii":[],"keywords":[]},"tone2":{"unicode":"1f3fc","unicode_alt":"","code_decimal":"🏼","name":"emoji modifier Fitzpatrick type-3","shortname":":tone2:","category":"modifier","emoji_order":"1076","aliases":[],"aliases_ascii":[],"keywords":[]},"tone3":{"unicode":"1f3fd","unicode_alt":"","code_decimal":"🏽","name":"emoji modifier Fitzpatrick type-4","shortname":":tone3:","category":"modifier","emoji_order":"1077","aliases":[],"aliases_ascii":[],"keywords":[]},"tone4":{"unicode":"1f3fe","unicode_alt":"","code_decimal":"🏾","name":"emoji modifier Fitzpatrick type-5","shortname":":tone4:","category":"modifier","emoji_order":"1078","aliases":[],"aliases_ascii":[],"keywords":[]},"tone5":{"unicode":"1f3ff","unicode_alt":"","code_decimal":"🏿","name":"emoji modifier Fitzpatrick type-6","shortname":":tone5:","category":"modifier","emoji_order":"1079","aliases":[],"aliases_ascii":[],"keywords":[]},"muscle":{"unicode":"1f4aa","unicode_alt":"","code_decimal":"💪","name":"flexed biceps","shortname":":muscle:","category":"people","emoji_order":"1080","aliases":[],"aliases_ascii":[],"keywords":["body","hands","workout","flex","win","diversity","feminist","boys night"]},"muscle_tone1":{"unicode":"1f4aa-1f3fb","unicode_alt":"","code_decimal":"💪🏻","name":"flexed biceps tone 1","shortname":":muscle_tone1:","category":"people","emoji_order":"1081","aliases":[],"aliases_ascii":[],"keywords":[]},"muscle_tone2":{"unicode":"1f4aa-1f3fc","unicode_alt":"","code_decimal":"💪🏼","name":"flexed biceps tone 2","shortname":":muscle_tone2:","category":"people","emoji_order":"1082","aliases":[],"aliases_ascii":[],"keywords":[]},"muscle_tone3":{"unicode":"1f4aa-1f3fd","unicode_alt":"","code_decimal":"💪🏽","name":"flexed biceps tone 3","shortname":":muscle_tone3:","category":"people","emoji_order":"1083","aliases":[],"aliases_ascii":[],"keywords":[]},"muscle_tone4":{"unicode":"1f4aa-1f3fe","unicode_alt":"","code_decimal":"💪🏾","name":"flexed biceps tone 4","shortname":":muscle_tone4:","category":"people","emoji_order":"1084","aliases":[],"aliases_ascii":[],"keywords":[]},"muscle_tone5":{"unicode":"1f4aa-1f3ff","unicode_alt":"","code_decimal":"💪🏿","name":"flexed biceps tone 5","shortname":":muscle_tone5:","category":"people","emoji_order":"1085","aliases":[],"aliases_ascii":[],"keywords":[]},"selfie":{"unicode":"1f933","unicode_alt":"","code_decimal":"🤳","name":"selfie","shortname":":selfie:","category":"people","emoji_order":"1086","aliases":[],"aliases_ascii":[],"keywords":[]},"selfie_tone1":{"unicode":"1f933-1f3fb","unicode_alt":"","code_decimal":"🤳🏻","name":"selfie tone 1","shortname":":selfie_tone1:","category":"people","emoji_order":"1087","aliases":[],"aliases_ascii":[],"keywords":[]},"selfie_tone2":{"unicode":"1f933-1f3fc","unicode_alt":"","code_decimal":"🤳🏼","name":"selfie tone 2","shortname":":selfie_tone2:","category":"people","emoji_order":"1088","aliases":[],"aliases_ascii":[],"keywords":[]},"selfie_tone3":{"unicode":"1f933-1f3fd","unicode_alt":"","code_decimal":"🤳🏽","name":"selfie tone 3","shortname":":selfie_tone3:","category":"people","emoji_order":"1089","aliases":[],"aliases_ascii":[],"keywords":[]},"selfie_tone4":{"unicode":"1f933-1f3fe","unicode_alt":"","code_decimal":"🤳🏾","name":"selfie tone 4","shortname":":selfie_tone4:","category":"people","emoji_order":"1090","aliases":[],"aliases_ascii":[],"keywords":[]},"selfie_tone5":{"unicode":"1f933-1f3ff","unicode_alt":"","code_decimal":"🤳🏿","name":"selfie tone 5","shortname":":selfie_tone5:","category":"people","emoji_order":"1091","aliases":[],"aliases_ascii":[],"keywords":[]},"point_left":{"unicode":"1f448","unicode_alt":"","code_decimal":"👈","name":"white left pointing backhand index","shortname":":point_left:","category":"people","emoji_order":"1092","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","diversity"]},"point_left_tone1":{"unicode":"1f448-1f3fb","unicode_alt":"","code_decimal":"👈🏻","name":"white left pointing backhand index tone 1","shortname":":point_left_tone1:","category":"people","emoji_order":"1093","aliases":[],"aliases_ascii":[],"keywords":[]},"point_left_tone2":{"unicode":"1f448-1f3fc","unicode_alt":"","code_decimal":"👈🏼","name":"white left pointing backhand index tone 2","shortname":":point_left_tone2:","category":"people","emoji_order":"1094","aliases":[],"aliases_ascii":[],"keywords":[]},"point_left_tone3":{"unicode":"1f448-1f3fd","unicode_alt":"","code_decimal":"👈🏽","name":"white left pointing backhand index tone 3","shortname":":point_left_tone3:","category":"people","emoji_order":"1095","aliases":[],"aliases_ascii":[],"keywords":[]},"point_left_tone4":{"unicode":"1f448-1f3fe","unicode_alt":"","code_decimal":"👈🏾","name":"white left pointing backhand index tone 4","shortname":":point_left_tone4:","category":"people","emoji_order":"1096","aliases":[],"aliases_ascii":[],"keywords":[]},"point_left_tone5":{"unicode":"1f448-1f3ff","unicode_alt":"","code_decimal":"👈🏿","name":"white left pointing backhand index tone 5","shortname":":point_left_tone5:","category":"people","emoji_order":"1097","aliases":[],"aliases_ascii":[],"keywords":[]},"point_right":{"unicode":"1f449","unicode_alt":"","code_decimal":"👉","name":"white right pointing backhand index","shortname":":point_right:","category":"people","emoji_order":"1098","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","diversity"]},"point_right_tone1":{"unicode":"1f449-1f3fb","unicode_alt":"","code_decimal":"👉🏻","name":"white right pointing backhand index tone 1","shortname":":point_right_tone1:","category":"people","emoji_order":"1099","aliases":[],"aliases_ascii":[],"keywords":[]},"point_right_tone2":{"unicode":"1f449-1f3fc","unicode_alt":"","code_decimal":"👉🏼","name":"white right pointing backhand index tone 2","shortname":":point_right_tone2:","category":"people","emoji_order":"1100","aliases":[],"aliases_ascii":[],"keywords":[]},"point_right_tone3":{"unicode":"1f449-1f3fd","unicode_alt":"","code_decimal":"👉🏽","name":"white right pointing backhand index tone 3","shortname":":point_right_tone3:","category":"people","emoji_order":"1101","aliases":[],"aliases_ascii":[],"keywords":[]},"point_right_tone4":{"unicode":"1f449-1f3fe","unicode_alt":"","code_decimal":"👉🏾","name":"white right pointing backhand index tone 4","shortname":":point_right_tone4:","category":"people","emoji_order":"1102","aliases":[],"aliases_ascii":[],"keywords":[]},"point_right_tone5":{"unicode":"1f449-1f3ff","unicode_alt":"","code_decimal":"👉🏿","name":"white right pointing backhand index tone 5","shortname":":point_right_tone5:","category":"people","emoji_order":"1103","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up":{"unicode":"261d","unicode_alt":"261d-fe0f","code_decimal":"☝","name":"white up pointing index","shortname":":point_up:","category":"people","emoji_order":"1104","aliases":[],"aliases_ascii":[],"keywords":["body","hands","emojione","diversity"]},"point_up_tone1":{"unicode":"261d-1f3fb","unicode_alt":"","code_decimal":"☝🏻","name":"white up pointing index tone 1","shortname":":point_up_tone1:","category":"people","emoji_order":"1105","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_tone2":{"unicode":"261d-1f3fc","unicode_alt":"","code_decimal":"☝🏼","name":"white up pointing index tone 2","shortname":":point_up_tone2:","category":"people","emoji_order":"1106","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_tone3":{"unicode":"261d-1f3fd","unicode_alt":"","code_decimal":"☝🏽","name":"white up pointing index tone 3","shortname":":point_up_tone3:","category":"people","emoji_order":"1107","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_tone4":{"unicode":"261d-1f3fe","unicode_alt":"","code_decimal":"☝🏾","name":"white up pointing index tone 4","shortname":":point_up_tone4:","category":"people","emoji_order":"1108","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_tone5":{"unicode":"261d-1f3ff","unicode_alt":"","code_decimal":"☝🏿","name":"white up pointing index tone 5","shortname":":point_up_tone5:","category":"people","emoji_order":"1109","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_2":{"unicode":"1f446","unicode_alt":"","code_decimal":"👆","name":"white up pointing backhand index","shortname":":point_up_2:","category":"people","emoji_order":"1110","aliases":[],"aliases_ascii":[],"keywords":["body","hands","diversity"]},"point_up_2_tone1":{"unicode":"1f446-1f3fb","unicode_alt":"","code_decimal":"👆🏻","name":"white up pointing backhand index tone 1","shortname":":point_up_2_tone1:","category":"people","emoji_order":"1111","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_2_tone2":{"unicode":"1f446-1f3fc","unicode_alt":"","code_decimal":"👆🏼","name":"white up pointing backhand index tone 2","shortname":":point_up_2_tone2:","category":"people","emoji_order":"1112","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_2_tone3":{"unicode":"1f446-1f3fd","unicode_alt":"","code_decimal":"👆🏽","name":"white up pointing backhand index tone 3","shortname":":point_up_2_tone3:","category":"people","emoji_order":"1113","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_2_tone4":{"unicode":"1f446-1f3fe","unicode_alt":"","code_decimal":"👆🏾","name":"white up pointing backhand index tone 4","shortname":":point_up_2_tone4:","category":"people","emoji_order":"1114","aliases":[],"aliases_ascii":[],"keywords":[]},"point_up_2_tone5":{"unicode":"1f446-1f3ff","unicode_alt":"","code_decimal":"👆🏿","name":"white up pointing backhand index tone 5","shortname":":point_up_2_tone5:","category":"people","emoji_order":"1115","aliases":[],"aliases_ascii":[],"keywords":[]},"middle_finger":{"unicode":"1f595","unicode_alt":"","code_decimal":"🖕","name":"reversed hand with middle finger extended","shortname":":middle_finger:","category":"people","emoji_order":"1116","aliases":[":reversed_hand_with_middle_finger_extended:"],"aliases_ascii":[],"keywords":["body","hands","middle finger","diversity"]},"middle_finger_tone1":{"unicode":"1f595-1f3fb","unicode_alt":"","code_decimal":"🖕🏻","name":"reversed hand with middle finger extended tone 1","shortname":":middle_finger_tone1:","category":"people","emoji_order":"1117","aliases":[":reversed_hand_with_middle_finger_extended_tone1:"],"aliases_ascii":[],"keywords":[]},"middle_finger_tone2":{"unicode":"1f595-1f3fc","unicode_alt":"","code_decimal":"🖕🏼","name":"reversed hand with middle finger extended tone 2","shortname":":middle_finger_tone2:","category":"people","emoji_order":"1118","aliases":[":reversed_hand_with_middle_finger_extended_tone2:"],"aliases_ascii":[],"keywords":[]},"middle_finger_tone3":{"unicode":"1f595-1f3fd","unicode_alt":"","code_decimal":"🖕🏽","name":"reversed hand with middle finger extended tone 3","shortname":":middle_finger_tone3:","category":"people","emoji_order":"1119","aliases":[":reversed_hand_with_middle_finger_extended_tone3:"],"aliases_ascii":[],"keywords":[]},"middle_finger_tone4":{"unicode":"1f595-1f3fe","unicode_alt":"","code_decimal":"🖕🏾","name":"reversed hand with middle finger extended tone 4","shortname":":middle_finger_tone4:","category":"people","emoji_order":"1120","aliases":[":reversed_hand_with_middle_finger_extended_tone4:"],"aliases_ascii":[],"keywords":[]},"middle_finger_tone5":{"unicode":"1f595-1f3ff","unicode_alt":"","code_decimal":"🖕🏿","name":"reversed hand with middle finger extended tone 5","shortname":":middle_finger_tone5:","category":"people","emoji_order":"1121","aliases":[":reversed_hand_with_middle_finger_extended_tone5:"],"aliases_ascii":[],"keywords":[]},"point_down":{"unicode":"1f447","unicode_alt":"","code_decimal":"👇","name":"white down pointing backhand index","shortname":":point_down:","category":"people","emoji_order":"1122","aliases":[],"aliases_ascii":[],"keywords":["body","hands","diversity"]},"point_down_tone1":{"unicode":"1f447-1f3fb","unicode_alt":"","code_decimal":"👇🏻","name":"white down pointing backhand index tone 1","shortname":":point_down_tone1:","category":"people","emoji_order":"1123","aliases":[],"aliases_ascii":[],"keywords":[]},"point_down_tone2":{"unicode":"1f447-1f3fc","unicode_alt":"","code_decimal":"👇🏼","name":"white down pointing backhand index tone 2","shortname":":point_down_tone2:","category":"people","emoji_order":"1124","aliases":[],"aliases_ascii":[],"keywords":[]},"point_down_tone3":{"unicode":"1f447-1f3fd","unicode_alt":"","code_decimal":"👇🏽","name":"white down pointing backhand index tone 3","shortname":":point_down_tone3:","category":"people","emoji_order":"1125","aliases":[],"aliases_ascii":[],"keywords":[]},"point_down_tone4":{"unicode":"1f447-1f3fe","unicode_alt":"","code_decimal":"👇🏾","name":"white down pointing backhand index tone 4","shortname":":point_down_tone4:","category":"people","emoji_order":"1126","aliases":[],"aliases_ascii":[],"keywords":[]},"point_down_tone5":{"unicode":"1f447-1f3ff","unicode_alt":"","code_decimal":"👇🏿","name":"white down pointing backhand index tone 5","shortname":":point_down_tone5:","category":"people","emoji_order":"1127","aliases":[],"aliases_ascii":[],"keywords":[]},"v":{"unicode":"270c","unicode_alt":"270c-fe0f","code_decimal":"✌","name":"victory hand","shortname":":v:","category":"people","emoji_order":"1128","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","thank you","peace","diversity","girls night"]},"v_tone1":{"unicode":"270c-1f3fb","unicode_alt":"","code_decimal":"✌🏻","name":"victory hand tone 1","shortname":":v_tone1:","category":"people","emoji_order":"1129","aliases":[],"aliases_ascii":[],"keywords":[]},"v_tone2":{"unicode":"270c-1f3fc","unicode_alt":"","code_decimal":"✌🏼","name":"victory hand tone 2","shortname":":v_tone2:","category":"people","emoji_order":"1130","aliases":[],"aliases_ascii":[],"keywords":[]},"v_tone3":{"unicode":"270c-1f3fd","unicode_alt":"","code_decimal":"✌🏽","name":"victory hand tone 3","shortname":":v_tone3:","category":"people","emoji_order":"1131","aliases":[],"aliases_ascii":[],"keywords":[]},"v_tone4":{"unicode":"270c-1f3fe","unicode_alt":"","code_decimal":"✌🏾","name":"victory hand tone 4","shortname":":v_tone4:","category":"people","emoji_order":"1132","aliases":[],"aliases_ascii":[],"keywords":[]},"v_tone5":{"unicode":"270c-1f3ff","unicode_alt":"","code_decimal":"✌🏿","name":"victory hand tone 5","shortname":":v_tone5:","category":"people","emoji_order":"1133","aliases":[],"aliases_ascii":[],"keywords":[]},"fingers_crossed":{"unicode":"1f91e","unicode_alt":"","code_decimal":"🤞","name":"hand with first and index finger crossed","shortname":":fingers_crossed:","category":"people","emoji_order":"1134","aliases":[":hand_with_index_and_middle_finger_crossed:"],"aliases_ascii":[],"keywords":[]},"fingers_crossed_tone1":{"unicode":"1f91e-1f3fb","unicode_alt":"","code_decimal":"🤞🏻","name":"hand with index and middle fingers crossed tone 1","shortname":":fingers_crossed_tone1:","category":"people","emoji_order":"1135","aliases":[":hand_with_index_and_middle_fingers_crossed_tone1:"],"aliases_ascii":[],"keywords":[]},"fingers_crossed_tone2":{"unicode":"1f91e-1f3fc","unicode_alt":"","code_decimal":"🤞🏼","name":"hand with index and middle fingers crossed tone 2","shortname":":fingers_crossed_tone2:","category":"people","emoji_order":"1136","aliases":[":hand_with_index_and_middle_fingers_crossed_tone2:"],"aliases_ascii":[],"keywords":[]},"fingers_crossed_tone3":{"unicode":"1f91e-1f3fd","unicode_alt":"","code_decimal":"🤞🏽","name":"hand with index and middle fingers crossed tone 3","shortname":":fingers_crossed_tone3:","category":"people","emoji_order":"1137","aliases":[":hand_with_index_and_middle_fingers_crossed_tone3:"],"aliases_ascii":[],"keywords":[]},"fingers_crossed_tone4":{"unicode":"1f91e-1f3fe","unicode_alt":"","code_decimal":"🤞🏾","name":"hand with index and middle fingers crossed tone 4","shortname":":fingers_crossed_tone4:","category":"people","emoji_order":"1138","aliases":[":hand_with_index_and_middle_fingers_crossed_tone4:"],"aliases_ascii":[],"keywords":[]},"fingers_crossed_tone5":{"unicode":"1f91e-1f3ff","unicode_alt":"","code_decimal":"🤞🏿","name":"hand with index and middle fingers crossed tone 5","shortname":":fingers_crossed_tone5:","category":"people","emoji_order":"1139","aliases":[":hand_with_index_and_middle_fingers_crossed_tone5:"],"aliases_ascii":[],"keywords":[]},"vulcan":{"unicode":"1f596","unicode_alt":"","code_decimal":"🖖","name":"raised hand with part between middle and ring fingers","shortname":":vulcan:","category":"people","emoji_order":"1140","aliases":[":raised_hand_with_part_between_middle_and_ring_fingers:"],"aliases_ascii":[],"keywords":["body","hands","hi","diversity"]},"vulcan_tone1":{"unicode":"1f596-1f3fb","unicode_alt":"","code_decimal":"🖖🏻","name":"raised hand with part between middle and ring fingers tone 1","shortname":":vulcan_tone1:","category":"people","emoji_order":"1141","aliases":[":raised_hand_with_part_between_middle_and_ring_fingers_tone1:"],"aliases_ascii":[],"keywords":[]},"vulcan_tone2":{"unicode":"1f596-1f3fc","unicode_alt":"","code_decimal":"🖖🏼","name":"raised hand with part between middle and ring fingers tone 2","shortname":":vulcan_tone2:","category":"people","emoji_order":"1142","aliases":[":raised_hand_with_part_between_middle_and_ring_fingers_tone2:"],"aliases_ascii":[],"keywords":[]},"vulcan_tone3":{"unicode":"1f596-1f3fd","unicode_alt":"","code_decimal":"🖖🏽","name":"raised hand with part between middle and ring fingers tone 3","shortname":":vulcan_tone3:","category":"people","emoji_order":"1143","aliases":[":raised_hand_with_part_between_middle_and_ring_fingers_tone3:"],"aliases_ascii":[],"keywords":[]},"vulcan_tone4":{"unicode":"1f596-1f3fe","unicode_alt":"","code_decimal":"🖖🏾","name":"raised hand with part between middle and ring fingers tone 4","shortname":":vulcan_tone4:","category":"people","emoji_order":"1144","aliases":[":raised_hand_with_part_between_middle_and_ring_fingers_tone4:"],"aliases_ascii":[],"keywords":[]},"vulcan_tone5":{"unicode":"1f596-1f3ff","unicode_alt":"","code_decimal":"🖖🏿","name":"raised hand with part between middle and ring fingers tone 5","shortname":":vulcan_tone5:","category":"people","emoji_order":"1145","aliases":[":raised_hand_with_part_between_middle_and_ring_fingers_tone5:"],"aliases_ascii":[],"keywords":[]},"metal":{"unicode":"1f918","unicode_alt":"","code_decimal":"🤘","name":"sign of the horns","shortname":":metal:","category":"people","emoji_order":"1146","aliases":[":sign_of_the_horns:"],"aliases_ascii":[],"keywords":["body","hands","hi","diversity","boys night","parties"]},"metal_tone1":{"unicode":"1f918-1f3fb","unicode_alt":"","code_decimal":"🤘🏻","name":"sign of the horns tone 1","shortname":":metal_tone1:","category":"people","emoji_order":"1147","aliases":[":sign_of_the_horns_tone1:"],"aliases_ascii":[],"keywords":[]},"metal_tone2":{"unicode":"1f918-1f3fc","unicode_alt":"","code_decimal":"🤘🏼","name":"sign of the horns tone 2","shortname":":metal_tone2:","category":"people","emoji_order":"1148","aliases":[":sign_of_the_horns_tone2:"],"aliases_ascii":[],"keywords":[]},"metal_tone3":{"unicode":"1f918-1f3fd","unicode_alt":"","code_decimal":"🤘🏽","name":"sign of the horns tone 3","shortname":":metal_tone3:","category":"people","emoji_order":"1149","aliases":[":sign_of_the_horns_tone3:"],"aliases_ascii":[],"keywords":[]},"metal_tone4":{"unicode":"1f918-1f3fe","unicode_alt":"","code_decimal":"🤘🏾","name":"sign of the horns tone 4","shortname":":metal_tone4:","category":"people","emoji_order":"1150","aliases":[":sign_of_the_horns_tone4:"],"aliases_ascii":[],"keywords":[]},"metal_tone5":{"unicode":"1f918-1f3ff","unicode_alt":"","code_decimal":"🤘🏿","name":"sign of the horns tone 5","shortname":":metal_tone5:","category":"people","emoji_order":"1151","aliases":[":sign_of_the_horns_tone5:"],"aliases_ascii":[],"keywords":[]},"call_me":{"unicode":"1f919","unicode_alt":"","code_decimal":"🤙","name":"call me hand","shortname":":call_me:","category":"people","emoji_order":"1152","aliases":[":call_me_hand:"],"aliases_ascii":[],"keywords":[]},"call_me_tone1":{"unicode":"1f919-1f3fb","unicode_alt":"","code_decimal":"🤙🏻","name":"call me hand tone 1","shortname":":call_me_tone1:","category":"people","emoji_order":"1153","aliases":[":call_me_hand_tone1:"],"aliases_ascii":[],"keywords":[]},"call_me_tone2":{"unicode":"1f919-1f3fc","unicode_alt":"","code_decimal":"🤙🏼","name":"call me hand tone 2","shortname":":call_me_tone2:","category":"people","emoji_order":"1154","aliases":[":call_me_hand_tone2:"],"aliases_ascii":[],"keywords":[]},"call_me_tone3":{"unicode":"1f919-1f3fd","unicode_alt":"","code_decimal":"🤙🏽","name":"call me hand tone 3","shortname":":call_me_tone3:","category":"people","emoji_order":"1155","aliases":[":call_me_hand_tone3:"],"aliases_ascii":[],"keywords":[]},"call_me_tone4":{"unicode":"1f919-1f3fe","unicode_alt":"","code_decimal":"🤙🏾","name":"call me hand tone 4","shortname":":call_me_tone4:","category":"people","emoji_order":"1156","aliases":[":call_me_hand_tone4:"],"aliases_ascii":[],"keywords":[]},"call_me_tone5":{"unicode":"1f919-1f3ff","unicode_alt":"","code_decimal":"🤙🏿","name":"call me hand tone 5","shortname":":call_me_tone5:","category":"people","emoji_order":"1157","aliases":[":call_me_hand_tone5:"],"aliases_ascii":[],"keywords":[]},"hand_splayed":{"unicode":"1f590","unicode_alt":"1f590-fe0f","code_decimal":"🖐","name":"raised hand with fingers splayed","shortname":":hand_splayed:","category":"people","emoji_order":"1158","aliases":[":raised_hand_with_fingers_splayed:"],"aliases_ascii":[],"keywords":["body","hands","hi","diversity"]},"hand_splayed_tone1":{"unicode":"1f590-1f3fb","unicode_alt":"","code_decimal":"🖐🏻","name":"raised hand with fingers splayed tone 1","shortname":":hand_splayed_tone1:","category":"people","emoji_order":"1159","aliases":[":raised_hand_with_fingers_splayed_tone1:"],"aliases_ascii":[],"keywords":[]},"hand_splayed_tone2":{"unicode":"1f590-1f3fc","unicode_alt":"","code_decimal":"🖐🏼","name":"raised hand with fingers splayed tone 2","shortname":":hand_splayed_tone2:","category":"people","emoji_order":"1160","aliases":[":raised_hand_with_fingers_splayed_tone2:"],"aliases_ascii":[],"keywords":[]},"hand_splayed_tone3":{"unicode":"1f590-1f3fd","unicode_alt":"","code_decimal":"🖐🏽","name":"raised hand with fingers splayed tone 3","shortname":":hand_splayed_tone3:","category":"people","emoji_order":"1161","aliases":[":raised_hand_with_fingers_splayed_tone3:"],"aliases_ascii":[],"keywords":[]},"hand_splayed_tone4":{"unicode":"1f590-1f3fe","unicode_alt":"","code_decimal":"🖐🏾","name":"raised hand with fingers splayed tone 4","shortname":":hand_splayed_tone4:","category":"people","emoji_order":"1162","aliases":[":raised_hand_with_fingers_splayed_tone4:"],"aliases_ascii":[],"keywords":[]},"hand_splayed_tone5":{"unicode":"1f590-1f3ff","unicode_alt":"","code_decimal":"🖐🏿","name":"raised hand with fingers splayed tone 5","shortname":":hand_splayed_tone5:","category":"people","emoji_order":"1163","aliases":[":raised_hand_with_fingers_splayed_tone5:"],"aliases_ascii":[],"keywords":[]},"raised_hand":{"unicode":"270b","unicode_alt":"","code_decimal":"✋","name":"raised hand","shortname":":raised_hand:","category":"people","emoji_order":"1164","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","diversity","girls night"]},"raised_hand_tone1":{"unicode":"270b-1f3fb","unicode_alt":"","code_decimal":"✋🏻","name":"raised hand tone 1","shortname":":raised_hand_tone1:","category":"people","emoji_order":"1165","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hand_tone2":{"unicode":"270b-1f3fc","unicode_alt":"","code_decimal":"✋🏼","name":"raised hand tone 2","shortname":":raised_hand_tone2:","category":"people","emoji_order":"1166","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hand_tone3":{"unicode":"270b-1f3fd","unicode_alt":"","code_decimal":"✋🏽","name":"raised hand tone 3","shortname":":raised_hand_tone3:","category":"people","emoji_order":"1167","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hand_tone4":{"unicode":"270b-1f3fe","unicode_alt":"","code_decimal":"✋🏾","name":"raised hand tone 4","shortname":":raised_hand_tone4:","category":"people","emoji_order":"1168","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hand_tone5":{"unicode":"270b-1f3ff","unicode_alt":"","code_decimal":"✋🏿","name":"raised hand tone 5","shortname":":raised_hand_tone5:","category":"people","emoji_order":"1169","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_hand":{"unicode":"1f44c","unicode_alt":"","code_decimal":"👌","name":"ok hand sign","shortname":":ok_hand:","category":"people","emoji_order":"1170","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","diversity","perfect","good","beautiful"]},"ok_hand_tone1":{"unicode":"1f44c-1f3fb","unicode_alt":"","code_decimal":"👌🏻","name":"ok hand sign tone 1","shortname":":ok_hand_tone1:","category":"people","emoji_order":"1171","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_hand_tone2":{"unicode":"1f44c-1f3fc","unicode_alt":"","code_decimal":"👌🏼","name":"ok hand sign tone 2","shortname":":ok_hand_tone2:","category":"people","emoji_order":"1172","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_hand_tone3":{"unicode":"1f44c-1f3fd","unicode_alt":"","code_decimal":"👌🏽","name":"ok hand sign tone 3","shortname":":ok_hand_tone3:","category":"people","emoji_order":"1173","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_hand_tone4":{"unicode":"1f44c-1f3fe","unicode_alt":"","code_decimal":"👌🏾","name":"ok hand sign tone 4","shortname":":ok_hand_tone4:","category":"people","emoji_order":"1174","aliases":[],"aliases_ascii":[],"keywords":[]},"ok_hand_tone5":{"unicode":"1f44c-1f3ff","unicode_alt":"","code_decimal":"👌🏿","name":"ok hand sign tone 5","shortname":":ok_hand_tone5:","category":"people","emoji_order":"1175","aliases":[],"aliases_ascii":[],"keywords":[]},"thumbsup":{"unicode":"1f44d","unicode_alt":"","code_decimal":"👍","name":"thumbs up sign","shortname":":thumbsup:","category":"people","emoji_order":"1176","aliases":[":+1:",":thumbup:"],"aliases_ascii":[],"keywords":["body","hands","hi","luck","thank you","diversity","perfect","good","beautiful"]},"thumbsup_tone1":{"unicode":"1f44d-1f3fb","unicode_alt":"","code_decimal":"👍🏻","name":"thumbs up sign tone 1","shortname":":thumbsup_tone1:","category":"people","emoji_order":"1177","aliases":[":+1_tone1:",":thumbup_tone1:"],"aliases_ascii":[],"keywords":[]},"thumbsup_tone2":{"unicode":"1f44d-1f3fc","unicode_alt":"","code_decimal":"👍🏼","name":"thumbs up sign tone 2","shortname":":thumbsup_tone2:","category":"people","emoji_order":"1178","aliases":[":+1_tone2:",":thumbup_tone2:"],"aliases_ascii":[],"keywords":[]},"thumbsup_tone3":{"unicode":"1f44d-1f3fd","unicode_alt":"","code_decimal":"👍🏽","name":"thumbs up sign tone 3","shortname":":thumbsup_tone3:","category":"people","emoji_order":"1179","aliases":[":+1_tone3:",":thumbup_tone3:"],"aliases_ascii":[],"keywords":[]},"thumbsup_tone4":{"unicode":"1f44d-1f3fe","unicode_alt":"","code_decimal":"👍🏾","name":"thumbs up sign tone 4","shortname":":thumbsup_tone4:","category":"people","emoji_order":"1180","aliases":[":+1_tone4:",":thumbup_tone4:"],"aliases_ascii":[],"keywords":[]},"thumbsup_tone5":{"unicode":"1f44d-1f3ff","unicode_alt":"","code_decimal":"👍🏿","name":"thumbs up sign tone 5","shortname":":thumbsup_tone5:","category":"people","emoji_order":"1181","aliases":[":+1_tone5:",":thumbup_tone5:"],"aliases_ascii":[],"keywords":[]},"thumbsdown":{"unicode":"1f44e","unicode_alt":"","code_decimal":"👎","name":"thumbs down sign","shortname":":thumbsdown:","category":"people","emoji_order":"1182","aliases":[":-1:",":thumbdown:"],"aliases_ascii":[],"keywords":["body","hands","diversity"]},"thumbsdown_tone1":{"unicode":"1f44e-1f3fb","unicode_alt":"","code_decimal":"👎🏻","name":"thumbs down sign tone 1","shortname":":thumbsdown_tone1:","category":"people","emoji_order":"1183","aliases":[":-1_tone1:",":thumbdown_tone1:"],"aliases_ascii":[],"keywords":[]},"thumbsdown_tone2":{"unicode":"1f44e-1f3fc","unicode_alt":"","code_decimal":"👎🏼","name":"thumbs down sign tone 2","shortname":":thumbsdown_tone2:","category":"people","emoji_order":"1184","aliases":[":-1_tone2:",":thumbdown_tone2:"],"aliases_ascii":[],"keywords":[]},"thumbsdown_tone3":{"unicode":"1f44e-1f3fd","unicode_alt":"","code_decimal":"👎🏽","name":"thumbs down sign tone 3","shortname":":thumbsdown_tone3:","category":"people","emoji_order":"1185","aliases":[":-1_tone3:",":thumbdown_tone3:"],"aliases_ascii":[],"keywords":[]},"thumbsdown_tone4":{"unicode":"1f44e-1f3fe","unicode_alt":"","code_decimal":"👎🏾","name":"thumbs down sign tone 4","shortname":":thumbsdown_tone4:","category":"people","emoji_order":"1186","aliases":[":-1_tone4:",":thumbdown_tone4:"],"aliases_ascii":[],"keywords":[]},"thumbsdown_tone5":{"unicode":"1f44e-1f3ff","unicode_alt":"","code_decimal":"👎🏿","name":"thumbs down sign tone 5","shortname":":thumbsdown_tone5:","category":"people","emoji_order":"1187","aliases":[":-1_tone5:",":thumbdown_tone5:"],"aliases_ascii":[],"keywords":[]},"fist":{"unicode":"270a","unicode_alt":"","code_decimal":"✊","name":"raised fist","shortname":":fist:","category":"people","emoji_order":"1188","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","fist bump","diversity","condolence"]},"fist_tone1":{"unicode":"270a-1f3fb","unicode_alt":"","code_decimal":"✊🏻","name":"raised fist tone 1","shortname":":fist_tone1:","category":"people","emoji_order":"1189","aliases":[],"aliases_ascii":[],"keywords":[]},"fist_tone2":{"unicode":"270a-1f3fc","unicode_alt":"","code_decimal":"✊🏼","name":"raised fist tone 2","shortname":":fist_tone2:","category":"people","emoji_order":"1190","aliases":[],"aliases_ascii":[],"keywords":[]},"fist_tone3":{"unicode":"270a-1f3fd","unicode_alt":"","code_decimal":"✊🏽","name":"raised fist tone 3","shortname":":fist_tone3:","category":"people","emoji_order":"1191","aliases":[],"aliases_ascii":[],"keywords":[]},"fist_tone4":{"unicode":"270a-1f3fe","unicode_alt":"","code_decimal":"✊🏾","name":"raised fist tone 4","shortname":":fist_tone4:","category":"people","emoji_order":"1192","aliases":[],"aliases_ascii":[],"keywords":[]},"fist_tone5":{"unicode":"270a-1f3ff","unicode_alt":"","code_decimal":"✊🏿","name":"raised fist tone 5","shortname":":fist_tone5:","category":"people","emoji_order":"1193","aliases":[],"aliases_ascii":[],"keywords":[]},"punch":{"unicode":"1f44a","unicode_alt":"","code_decimal":"👊","name":"fisted hand sign","shortname":":punch:","category":"people","emoji_order":"1194","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","fist bump","diversity","boys night"]},"punch_tone1":{"unicode":"1f44a-1f3fb","unicode_alt":"","code_decimal":"👊🏻","name":"fisted hand sign tone 1","shortname":":punch_tone1:","category":"people","emoji_order":"1195","aliases":[],"aliases_ascii":[],"keywords":[]},"punch_tone2":{"unicode":"1f44a-1f3fc","unicode_alt":"","code_decimal":"👊🏼","name":"fisted hand sign tone 2","shortname":":punch_tone2:","category":"people","emoji_order":"1196","aliases":[],"aliases_ascii":[],"keywords":[]},"punch_tone3":{"unicode":"1f44a-1f3fd","unicode_alt":"","code_decimal":"👊🏽","name":"fisted hand sign tone 3","shortname":":punch_tone3:","category":"people","emoji_order":"1197","aliases":[],"aliases_ascii":[],"keywords":[]},"punch_tone4":{"unicode":"1f44a-1f3fe","unicode_alt":"","code_decimal":"👊🏾","name":"fisted hand sign tone 4","shortname":":punch_tone4:","category":"people","emoji_order":"1198","aliases":[],"aliases_ascii":[],"keywords":[]},"punch_tone5":{"unicode":"1f44a-1f3ff","unicode_alt":"","code_decimal":"👊🏿","name":"fisted hand sign tone 5","shortname":":punch_tone5:","category":"people","emoji_order":"1199","aliases":[],"aliases_ascii":[],"keywords":[]},"left_facing_fist":{"unicode":"1f91b","unicode_alt":"","code_decimal":"🤛","name":"left-facing fist","shortname":":left_facing_fist:","category":"people","emoji_order":"1200","aliases":[":left_fist:"],"aliases_ascii":[],"keywords":[]},"left_facing_fist_tone1":{"unicode":"1f91b-1f3fb","unicode_alt":"","code_decimal":"🤛🏻","name":"left facing fist tone 1","shortname":":left_facing_fist_tone1:","category":"people","emoji_order":"1201","aliases":[":left_fist_tone1:"],"aliases_ascii":[],"keywords":[]},"left_facing_fist_tone2":{"unicode":"1f91b-1f3fc","unicode_alt":"","code_decimal":"🤛🏼","name":"left facing fist tone 2","shortname":":left_facing_fist_tone2:","category":"people","emoji_order":"1202","aliases":[":left_fist_tone2:"],"aliases_ascii":[],"keywords":[]},"left_facing_fist_tone3":{"unicode":"1f91b-1f3fd","unicode_alt":"","code_decimal":"🤛🏽","name":"left facing fist tone 3","shortname":":left_facing_fist_tone3:","category":"people","emoji_order":"1203","aliases":[":left_fist_tone3:"],"aliases_ascii":[],"keywords":[]},"left_facing_fist_tone4":{"unicode":"1f91b-1f3fe","unicode_alt":"","code_decimal":"🤛🏾","name":"left facing fist tone 4","shortname":":left_facing_fist_tone4:","category":"people","emoji_order":"1204","aliases":[":left_fist_tone4:"],"aliases_ascii":[],"keywords":[]},"left_facing_fist_tone5":{"unicode":"1f91b-1f3ff","unicode_alt":"","code_decimal":"🤛🏿","name":"left facing fist tone 5","shortname":":left_facing_fist_tone5:","category":"people","emoji_order":"1205","aliases":[":left_fist_tone5:"],"aliases_ascii":[],"keywords":[]},"right_facing_fist":{"unicode":"1f91c","unicode_alt":"","code_decimal":"🤜","name":"right-facing fist","shortname":":right_facing_fist:","category":"people","emoji_order":"1206","aliases":[":right_fist:"],"aliases_ascii":[],"keywords":[]},"right_facing_fist_tone1":{"unicode":"1f91c-1f3fb","unicode_alt":"","code_decimal":"🤜🏻","name":"right facing fist tone 1","shortname":":right_facing_fist_tone1:","category":"people","emoji_order":"1207","aliases":[":right_fist_tone1:"],"aliases_ascii":[],"keywords":[]},"right_facing_fist_tone2":{"unicode":"1f91c-1f3fc","unicode_alt":"","code_decimal":"🤜🏼","name":"right facing fist tone 2","shortname":":right_facing_fist_tone2:","category":"people","emoji_order":"1208","aliases":[":right_fist_tone2:"],"aliases_ascii":[],"keywords":[]},"right_facing_fist_tone3":{"unicode":"1f91c-1f3fd","unicode_alt":"","code_decimal":"🤜🏽","name":"right facing fist tone 3","shortname":":right_facing_fist_tone3:","category":"people","emoji_order":"1209","aliases":[":right_fist_tone3:"],"aliases_ascii":[],"keywords":[]},"right_facing_fist_tone4":{"unicode":"1f91c-1f3fe","unicode_alt":"","code_decimal":"🤜🏾","name":"right facing fist tone 4","shortname":":right_facing_fist_tone4:","category":"people","emoji_order":"1210","aliases":[":right_fist_tone4:"],"aliases_ascii":[],"keywords":[]},"right_facing_fist_tone5":{"unicode":"1f91c-1f3ff","unicode_alt":"","code_decimal":"🤜🏿","name":"right facing fist tone 5","shortname":":right_facing_fist_tone5:","category":"people","emoji_order":"1211","aliases":[":right_fist_tone5:"],"aliases_ascii":[],"keywords":[]},"raised_back_of_hand":{"unicode":"1f91a","unicode_alt":"","code_decimal":"🤚","name":"raised back of hand","shortname":":raised_back_of_hand:","category":"people","emoji_order":"1212","aliases":[":back_of_hand:"],"aliases_ascii":[],"keywords":[]},"raised_back_of_hand_tone1":{"unicode":"1f91a-1f3fb","unicode_alt":"","code_decimal":"🤚🏻","name":"raised back of hand tone 1","shortname":":raised_back_of_hand_tone1:","category":"people","emoji_order":"1213","aliases":[":back_of_hand_tone1:"],"aliases_ascii":[],"keywords":[]},"raised_back_of_hand_tone2":{"unicode":"1f91a-1f3fc","unicode_alt":"","code_decimal":"🤚🏼","name":"raised back of hand tone 2","shortname":":raised_back_of_hand_tone2:","category":"people","emoji_order":"1214","aliases":[":back_of_hand_tone2:"],"aliases_ascii":[],"keywords":[]},"raised_back_of_hand_tone3":{"unicode":"1f91a-1f3fd","unicode_alt":"","code_decimal":"🤚🏽","name":"raised back of hand tone 3","shortname":":raised_back_of_hand_tone3:","category":"people","emoji_order":"1215","aliases":[":back_of_hand_tone3:"],"aliases_ascii":[],"keywords":[]},"raised_back_of_hand_tone4":{"unicode":"1f91a-1f3fe","unicode_alt":"","code_decimal":"🤚🏾","name":"raised back of hand tone 4","shortname":":raised_back_of_hand_tone4:","category":"people","emoji_order":"1216","aliases":[":back_of_hand_tone4:"],"aliases_ascii":[],"keywords":[]},"raised_back_of_hand_tone5":{"unicode":"1f91a-1f3ff","unicode_alt":"","code_decimal":"🤚🏿","name":"raised back of hand tone 5","shortname":":raised_back_of_hand_tone5:","category":"people","emoji_order":"1217","aliases":[":back_of_hand_tone5:"],"aliases_ascii":[],"keywords":[]},"wave":{"unicode":"1f44b","unicode_alt":"","code_decimal":"👋","name":"waving hand sign","shortname":":wave:","category":"people","emoji_order":"1218","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","diversity"]},"wave_tone1":{"unicode":"1f44b-1f3fb","unicode_alt":"","code_decimal":"👋🏻","name":"waving hand sign tone 1","shortname":":wave_tone1:","category":"people","emoji_order":"1219","aliases":[],"aliases_ascii":[],"keywords":[]},"wave_tone2":{"unicode":"1f44b-1f3fc","unicode_alt":"","code_decimal":"👋🏼","name":"waving hand sign tone 2","shortname":":wave_tone2:","category":"people","emoji_order":"1220","aliases":[],"aliases_ascii":[],"keywords":[]},"wave_tone3":{"unicode":"1f44b-1f3fd","unicode_alt":"","code_decimal":"👋🏽","name":"waving hand sign tone 3","shortname":":wave_tone3:","category":"people","emoji_order":"1221","aliases":[],"aliases_ascii":[],"keywords":[]},"wave_tone4":{"unicode":"1f44b-1f3fe","unicode_alt":"","code_decimal":"👋🏾","name":"waving hand sign tone 4","shortname":":wave_tone4:","category":"people","emoji_order":"1222","aliases":[],"aliases_ascii":[],"keywords":[]},"wave_tone5":{"unicode":"1f44b-1f3ff","unicode_alt":"","code_decimal":"👋🏿","name":"waving hand sign tone 5","shortname":":wave_tone5:","category":"people","emoji_order":"1223","aliases":[],"aliases_ascii":[],"keywords":[]},"clap":{"unicode":"1f44f","unicode_alt":"","code_decimal":"👏","name":"clapping hands sign","shortname":":clap:","category":"people","emoji_order":"1224","aliases":[],"aliases_ascii":[],"keywords":["body","hands","win","diversity","good","beautiful"]},"clap_tone1":{"unicode":"1f44f-1f3fb","unicode_alt":"","code_decimal":"👏🏻","name":"clapping hands sign tone 1","shortname":":clap_tone1:","category":"people","emoji_order":"1225","aliases":[],"aliases_ascii":[],"keywords":[]},"clap_tone2":{"unicode":"1f44f-1f3fc","unicode_alt":"","code_decimal":"👏🏼","name":"clapping hands sign tone 2","shortname":":clap_tone2:","category":"people","emoji_order":"1226","aliases":[],"aliases_ascii":[],"keywords":[]},"clap_tone3":{"unicode":"1f44f-1f3fd","unicode_alt":"","code_decimal":"👏🏽","name":"clapping hands sign tone 3","shortname":":clap_tone3:","category":"people","emoji_order":"1227","aliases":[],"aliases_ascii":[],"keywords":[]},"clap_tone4":{"unicode":"1f44f-1f3fe","unicode_alt":"","code_decimal":"👏🏾","name":"clapping hands sign tone 4","shortname":":clap_tone4:","category":"people","emoji_order":"1228","aliases":[],"aliases_ascii":[],"keywords":[]},"clap_tone5":{"unicode":"1f44f-1f3ff","unicode_alt":"","code_decimal":"👏🏿","name":"clapping hands sign tone 5","shortname":":clap_tone5:","category":"people","emoji_order":"1229","aliases":[],"aliases_ascii":[],"keywords":[]},"writing_hand":{"unicode":"270d","unicode_alt":"270d-fe0f","code_decimal":"✍","name":"writing hand","shortname":":writing_hand:","category":"people","emoji_order":"1230","aliases":[],"aliases_ascii":[],"keywords":["body","hands","write","diversity"]},"writing_hand_tone1":{"unicode":"270d-1f3fb","unicode_alt":"","code_decimal":"✍🏻","name":"writing hand tone 1","shortname":":writing_hand_tone1:","category":"people","emoji_order":"1231","aliases":[],"aliases_ascii":[],"keywords":[]},"writing_hand_tone2":{"unicode":"270d-1f3fc","unicode_alt":"","code_decimal":"✍🏼","name":"writing hand tone 2","shortname":":writing_hand_tone2:","category":"people","emoji_order":"1232","aliases":[],"aliases_ascii":[],"keywords":[]},"writing_hand_tone3":{"unicode":"270d-1f3fd","unicode_alt":"","code_decimal":"✍🏽","name":"writing hand tone 3","shortname":":writing_hand_tone3:","category":"people","emoji_order":"1233","aliases":[],"aliases_ascii":[],"keywords":[]},"writing_hand_tone4":{"unicode":"270d-1f3fe","unicode_alt":"","code_decimal":"✍🏾","name":"writing hand tone 4","shortname":":writing_hand_tone4:","category":"people","emoji_order":"1234","aliases":[],"aliases_ascii":[],"keywords":[]},"writing_hand_tone5":{"unicode":"270d-1f3ff","unicode_alt":"","code_decimal":"✍🏿","name":"writing hand tone 5","shortname":":writing_hand_tone5:","category":"people","emoji_order":"1235","aliases":[],"aliases_ascii":[],"keywords":[]},"open_hands":{"unicode":"1f450","unicode_alt":"","code_decimal":"👐","name":"open hands sign","shortname":":open_hands:","category":"people","emoji_order":"1236","aliases":[],"aliases_ascii":[],"keywords":["body","hands","diversity","condolence"]},"open_hands_tone1":{"unicode":"1f450-1f3fb","unicode_alt":"","code_decimal":"👐🏻","name":"open hands sign tone 1","shortname":":open_hands_tone1:","category":"people","emoji_order":"1237","aliases":[],"aliases_ascii":[],"keywords":[]},"open_hands_tone2":{"unicode":"1f450-1f3fc","unicode_alt":"","code_decimal":"👐🏼","name":"open hands sign tone 2","shortname":":open_hands_tone2:","category":"people","emoji_order":"1238","aliases":[],"aliases_ascii":[],"keywords":[]},"open_hands_tone3":{"unicode":"1f450-1f3fd","unicode_alt":"","code_decimal":"👐🏽","name":"open hands sign tone 3","shortname":":open_hands_tone3:","category":"people","emoji_order":"1239","aliases":[],"aliases_ascii":[],"keywords":[]},"open_hands_tone4":{"unicode":"1f450-1f3fe","unicode_alt":"","code_decimal":"👐🏾","name":"open hands sign tone 4","shortname":":open_hands_tone4:","category":"people","emoji_order":"1240","aliases":[],"aliases_ascii":[],"keywords":[]},"open_hands_tone5":{"unicode":"1f450-1f3ff","unicode_alt":"","code_decimal":"👐🏿","name":"open hands sign tone 5","shortname":":open_hands_tone5:","category":"people","emoji_order":"1241","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hands":{"unicode":"1f64c","unicode_alt":"","code_decimal":"🙌","name":"person raising both hands in celebration","shortname":":raised_hands:","category":"people","emoji_order":"1242","aliases":[],"aliases_ascii":[],"keywords":["body","hands","diversity","perfect","good","parties"]},"raised_hands_tone1":{"unicode":"1f64c-1f3fb","unicode_alt":"","code_decimal":"🙌🏻","name":"person raising both hands in celebration tone 1","shortname":":raised_hands_tone1:","category":"people","emoji_order":"1243","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hands_tone2":{"unicode":"1f64c-1f3fc","unicode_alt":"","code_decimal":"🙌🏼","name":"person raising both hands in celebration tone 2","shortname":":raised_hands_tone2:","category":"people","emoji_order":"1244","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hands_tone3":{"unicode":"1f64c-1f3fd","unicode_alt":"","code_decimal":"🙌🏽","name":"person raising both hands in celebration tone 3","shortname":":raised_hands_tone3:","category":"people","emoji_order":"1245","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hands_tone4":{"unicode":"1f64c-1f3fe","unicode_alt":"","code_decimal":"🙌🏾","name":"person raising both hands in celebration tone 4","shortname":":raised_hands_tone4:","category":"people","emoji_order":"1246","aliases":[],"aliases_ascii":[],"keywords":[]},"raised_hands_tone5":{"unicode":"1f64c-1f3ff","unicode_alt":"","code_decimal":"🙌🏿","name":"person raising both hands in celebration tone 5","shortname":":raised_hands_tone5:","category":"people","emoji_order":"1247","aliases":[],"aliases_ascii":[],"keywords":[]},"pray":{"unicode":"1f64f","unicode_alt":"","code_decimal":"🙏","name":"person with folded hands","shortname":":pray:","category":"people","emoji_order":"1248","aliases":[],"aliases_ascii":[],"keywords":["body","hands","hi","luck","thank you","pray","diversity","scientology"]},"pray_tone1":{"unicode":"1f64f-1f3fb","unicode_alt":"","code_decimal":"🙏🏻","name":"person with folded hands tone 1","shortname":":pray_tone1:","category":"people","emoji_order":"1249","aliases":[],"aliases_ascii":[],"keywords":[]},"pray_tone2":{"unicode":"1f64f-1f3fc","unicode_alt":"","code_decimal":"🙏🏼","name":"person with folded hands tone 2","shortname":":pray_tone2:","category":"people","emoji_order":"1250","aliases":[],"aliases_ascii":[],"keywords":[]},"pray_tone3":{"unicode":"1f64f-1f3fd","unicode_alt":"","code_decimal":"🙏🏽","name":"person with folded hands tone 3","shortname":":pray_tone3:","category":"people","emoji_order":"1251","aliases":[],"aliases_ascii":[],"keywords":[]},"pray_tone4":{"unicode":"1f64f-1f3fe","unicode_alt":"","code_decimal":"🙏🏾","name":"person with folded hands tone 4","shortname":":pray_tone4:","category":"people","emoji_order":"1252","aliases":[],"aliases_ascii":[],"keywords":[]},"pray_tone5":{"unicode":"1f64f-1f3ff","unicode_alt":"","code_decimal":"🙏🏿","name":"person with folded hands tone 5","shortname":":pray_tone5:","category":"people","emoji_order":"1253","aliases":[],"aliases_ascii":[],"keywords":[]},"handshake":{"unicode":"1f91d","unicode_alt":"","code_decimal":"🤝","name":"handshake","shortname":":handshake:","category":"people","emoji_order":"1254","aliases":[":shaking_hands:"],"aliases_ascii":[],"keywords":[]},"handshake_tone1":{"unicode":"1f91d-1f3fb","unicode_alt":"","code_decimal":"🤝🏻","name":"handshake tone 1","shortname":":handshake_tone1:","category":"people","emoji_order":"1255","aliases":[":shaking_hands_tone1:"],"aliases_ascii":[],"keywords":[]},"handshake_tone2":{"unicode":"1f91d-1f3fc","unicode_alt":"","code_decimal":"🤝🏼","name":"handshake tone 2","shortname":":handshake_tone2:","category":"people","emoji_order":"1256","aliases":[":shaking_hands_tone2:"],"aliases_ascii":[],"keywords":[]},"handshake_tone3":{"unicode":"1f91d-1f3fd","unicode_alt":"","code_decimal":"🤝🏽","name":"handshake tone 3","shortname":":handshake_tone3:","category":"people","emoji_order":"1257","aliases":[":shaking_hands_tone3:"],"aliases_ascii":[],"keywords":[]},"handshake_tone4":{"unicode":"1f91d-1f3fe","unicode_alt":"","code_decimal":"🤝🏾","name":"handshake tone 4","shortname":":handshake_tone4:","category":"people","emoji_order":"1258","aliases":[":shaking_hands_tone4:"],"aliases_ascii":[],"keywords":[]},"handshake_tone5":{"unicode":"1f91d-1f3ff","unicode_alt":"","code_decimal":"🤝🏿","name":"handshake tone 5","shortname":":handshake_tone5:","category":"people","emoji_order":"1259","aliases":[":shaking_hands_tone5:"],"aliases_ascii":[],"keywords":[]},"nail_care":{"unicode":"1f485","unicode_alt":"","code_decimal":"💅","name":"nail polish","shortname":":nail_care:","category":"people","emoji_order":"1260","aliases":[],"aliases_ascii":[],"keywords":["women","body","hands","nailpolish","diversity","girls night"]},"nail_care_tone1":{"unicode":"1f485-1f3fb","unicode_alt":"","code_decimal":"💅🏻","name":"nail polish tone 1","shortname":":nail_care_tone1:","category":"people","emoji_order":"1261","aliases":[],"aliases_ascii":[],"keywords":[]},"nail_care_tone2":{"unicode":"1f485-1f3fc","unicode_alt":"","code_decimal":"💅🏼","name":"nail polish tone 2","shortname":":nail_care_tone2:","category":"people","emoji_order":"1262","aliases":[],"aliases_ascii":[],"keywords":[]},"nail_care_tone3":{"unicode":"1f485-1f3fd","unicode_alt":"","code_decimal":"💅🏽","name":"nail polish tone 3","shortname":":nail_care_tone3:","category":"people","emoji_order":"1263","aliases":[],"aliases_ascii":[],"keywords":[]},"nail_care_tone4":{"unicode":"1f485-1f3fe","unicode_alt":"","code_decimal":"💅🏾","name":"nail polish tone 4","shortname":":nail_care_tone4:","category":"people","emoji_order":"1264","aliases":[],"aliases_ascii":[],"keywords":[]},"nail_care_tone5":{"unicode":"1f485-1f3ff","unicode_alt":"","code_decimal":"💅🏿","name":"nail polish tone 5","shortname":":nail_care_tone5:","category":"people","emoji_order":"1265","aliases":[],"aliases_ascii":[],"keywords":[]},"ear":{"unicode":"1f442","unicode_alt":"","code_decimal":"👂","name":"ear","shortname":":ear:","category":"people","emoji_order":"1266","aliases":[],"aliases_ascii":[],"keywords":["body","diversity"]},"ear_tone1":{"unicode":"1f442-1f3fb","unicode_alt":"","code_decimal":"👂🏻","name":"ear tone 1","shortname":":ear_tone1:","category":"people","emoji_order":"1267","aliases":[],"aliases_ascii":[],"keywords":[]},"ear_tone2":{"unicode":"1f442-1f3fc","unicode_alt":"","code_decimal":"👂🏼","name":"ear tone 2","shortname":":ear_tone2:","category":"people","emoji_order":"1268","aliases":[],"aliases_ascii":[],"keywords":[]},"ear_tone3":{"unicode":"1f442-1f3fd","unicode_alt":"","code_decimal":"👂🏽","name":"ear tone 3","shortname":":ear_tone3:","category":"people","emoji_order":"1269","aliases":[],"aliases_ascii":[],"keywords":[]},"ear_tone4":{"unicode":"1f442-1f3fe","unicode_alt":"","code_decimal":"👂🏾","name":"ear tone 4","shortname":":ear_tone4:","category":"people","emoji_order":"1270","aliases":[],"aliases_ascii":[],"keywords":[]},"ear_tone5":{"unicode":"1f442-1f3ff","unicode_alt":"","code_decimal":"👂🏿","name":"ear tone 5","shortname":":ear_tone5:","category":"people","emoji_order":"1271","aliases":[],"aliases_ascii":[],"keywords":[]},"nose":{"unicode":"1f443","unicode_alt":"","code_decimal":"👃","name":"nose","shortname":":nose:","category":"people","emoji_order":"1272","aliases":[],"aliases_ascii":[],"keywords":["body","diversity"]},"nose_tone1":{"unicode":"1f443-1f3fb","unicode_alt":"","code_decimal":"👃🏻","name":"nose tone 1","shortname":":nose_tone1:","category":"people","emoji_order":"1273","aliases":[],"aliases_ascii":[],"keywords":[]},"nose_tone2":{"unicode":"1f443-1f3fc","unicode_alt":"","code_decimal":"👃🏼","name":"nose tone 2","shortname":":nose_tone2:","category":"people","emoji_order":"1274","aliases":[],"aliases_ascii":[],"keywords":[]},"nose_tone3":{"unicode":"1f443-1f3fd","unicode_alt":"","code_decimal":"👃🏽","name":"nose tone 3","shortname":":nose_tone3:","category":"people","emoji_order":"1275","aliases":[],"aliases_ascii":[],"keywords":[]},"nose_tone4":{"unicode":"1f443-1f3fe","unicode_alt":"","code_decimal":"👃🏾","name":"nose tone 4","shortname":":nose_tone4:","category":"people","emoji_order":"1276","aliases":[],"aliases_ascii":[],"keywords":[]},"nose_tone5":{"unicode":"1f443-1f3ff","unicode_alt":"","code_decimal":"👃🏿","name":"nose tone 5","shortname":":nose_tone5:","category":"people","emoji_order":"1277","aliases":[],"aliases_ascii":[],"keywords":[]},"footprints":{"unicode":"1f463","unicode_alt":"","code_decimal":"👣","name":"footprints","shortname":":footprints:","category":"people","emoji_order":"1278","aliases":[],"aliases_ascii":[],"keywords":[]},"eyes":{"unicode":"1f440","unicode_alt":"","code_decimal":"👀","name":"eyes","shortname":":eyes:","category":"people","emoji_order":"1279","aliases":[],"aliases_ascii":[],"keywords":["body","eyes"]},"eye":{"unicode":"1f441","unicode_alt":"1f441-fe0f","code_decimal":"👁","name":"eye","shortname":":eye:","category":"people","emoji_order":"1280","aliases":[],"aliases_ascii":[],"keywords":["body","eyes"]},"eye_in_speech_bubble":{"unicode":"1f441-1f5e8","unicode_alt":"1f441-200d-1f5e8","code_decimal":"👁🗨","name":"eye in speech bubble","shortname":":eye_in_speech_bubble:","category":"symbols","emoji_order":"1281","aliases":[],"aliases_ascii":[],"keywords":["object","symbol","eyes","talk"]},"tongue":{"unicode":"1f445","unicode_alt":"","code_decimal":"👅","name":"tongue","shortname":":tongue:","category":"people","emoji_order":"1282","aliases":[],"aliases_ascii":[],"keywords":["body","sexy","lip"]},"lips":{"unicode":"1f444","unicode_alt":"","code_decimal":"👄","name":"mouth","shortname":":lips:","category":"people","emoji_order":"1283","aliases":[],"aliases_ascii":[],"keywords":["women","body","sexy","lip"]},"kiss":{"unicode":"1f48b","unicode_alt":"","code_decimal":"💋","name":"kiss mark","shortname":":kiss:","category":"people","emoji_order":"1284","aliases":[],"aliases_ascii":[],"keywords":["women","love","sexy","lip","beautiful","girls night"]},"cupid":{"unicode":"1f498","unicode_alt":"","code_decimal":"💘","name":"heart with arrow","shortname":":cupid:","category":"symbols","emoji_order":"1285","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"heart":{"unicode":"2764","unicode_alt":"2764-fe0f","code_decimal":"❤","name":"heavy black heart","shortname":":heart:","category":"symbols","emoji_order":"1286","aliases":[],"aliases_ascii":["<3"],"keywords":["love","symbol","parties"]},"heartbeat":{"unicode":"1f493","unicode_alt":"","code_decimal":"💓","name":"beating heart","shortname":":heartbeat:","category":"symbols","emoji_order":"1287","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"broken_heart":{"unicode":"1f494","unicode_alt":"","code_decimal":"💔","name":"broken heart","shortname":":broken_heart:","category":"symbols","emoji_order":"1288","aliases":[],"aliases_ascii":["<\/3"],"keywords":["love","symbol","heartbreak"]},"two_hearts":{"unicode":"1f495","unicode_alt":"","code_decimal":"💕","name":"two hearts","shortname":":two_hearts:","category":"symbols","emoji_order":"1289","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"sparkling_heart":{"unicode":"1f496","unicode_alt":"","code_decimal":"💖","name":"sparkling heart","shortname":":sparkling_heart:","category":"symbols","emoji_order":"1290","aliases":[],"aliases_ascii":[],"keywords":["love","symbol","girls night"]},"heartpulse":{"unicode":"1f497","unicode_alt":"","code_decimal":"💗","name":"growing heart","shortname":":heartpulse:","category":"symbols","emoji_order":"1291","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"blue_heart":{"unicode":"1f499","unicode_alt":"","code_decimal":"💙","name":"blue heart","shortname":":blue_heart:","category":"symbols","emoji_order":"1292","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"green_heart":{"unicode":"1f49a","unicode_alt":"","code_decimal":"💚","name":"green heart","shortname":":green_heart:","category":"symbols","emoji_order":"1293","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"yellow_heart":{"unicode":"1f49b","unicode_alt":"","code_decimal":"💛","name":"yellow heart","shortname":":yellow_heart:","category":"symbols","emoji_order":"1294","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"purple_heart":{"unicode":"1f49c","unicode_alt":"","code_decimal":"💜","name":"purple heart","shortname":":purple_heart:","category":"symbols","emoji_order":"1295","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"black_heart":{"unicode":"1f5a4","unicode_alt":"","code_decimal":"🖤","name":"black heart","shortname":":black_heart:","category":"symbols","emoji_order":"1296","aliases":[],"aliases_ascii":[],"keywords":[]},"gift_heart":{"unicode":"1f49d","unicode_alt":"","code_decimal":"💝","name":"heart with ribbon","shortname":":gift_heart:","category":"symbols","emoji_order":"1297","aliases":[],"aliases_ascii":[],"keywords":["love","symbol","condolence"]},"revolving_hearts":{"unicode":"1f49e","unicode_alt":"","code_decimal":"💞","name":"revolving hearts","shortname":":revolving_hearts:","category":"symbols","emoji_order":"1298","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"heart_decoration":{"unicode":"1f49f","unicode_alt":"","code_decimal":"💟","name":"heart decoration","shortname":":heart_decoration:","category":"symbols","emoji_order":"1299","aliases":[],"aliases_ascii":[],"keywords":["love","symbol"]},"heart_exclamation":{"unicode":"2763","unicode_alt":"2763-fe0f","code_decimal":"❣","name":"heavy heart exclamation mark ornament","shortname":":heart_exclamation:","category":"symbols","emoji_order":"1300","aliases":[":heavy_heart_exclamation_mark_ornament:"],"aliases_ascii":[],"keywords":["love","symbol"]},"love_letter":{"unicode":"1f48c","unicode_alt":"","code_decimal":"💌","name":"love letter","shortname":":love_letter:","category":"objects","emoji_order":"1301","aliases":[],"aliases_ascii":[],"keywords":["object"]},"zzz":{"unicode":"1f4a4","unicode_alt":"","code_decimal":"💤","name":"sleeping symbol","shortname":":zzz:","category":"people","emoji_order":"1302","aliases":[],"aliases_ascii":[],"keywords":["tired","goodnight"]},"anger":{"unicode":"1f4a2","unicode_alt":"","code_decimal":"💢","name":"anger symbol","shortname":":anger:","category":"symbols","emoji_order":"1303","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"bomb":{"unicode":"1f4a3","unicode_alt":"","code_decimal":"💣","name":"bomb","shortname":":bomb:","category":"objects","emoji_order":"1304","aliases":[],"aliases_ascii":[],"keywords":["object","weapon","dead","blast"]},"boom":{"unicode":"1f4a5","unicode_alt":"","code_decimal":"💥","name":"collision symbol","shortname":":boom:","category":"symbols","emoji_order":"1305","aliases":[],"aliases_ascii":[],"keywords":["symbol","blast"]},"sweat_drops":{"unicode":"1f4a6","unicode_alt":"","code_decimal":"💦","name":"splashing sweat symbol","shortname":":sweat_drops:","category":"nature","emoji_order":"1306","aliases":[],"aliases_ascii":[],"keywords":["rain","stressed","sweat"]},"dash":{"unicode":"1f4a8","unicode_alt":"","code_decimal":"💨","name":"dash symbol","shortname":":dash:","category":"nature","emoji_order":"1307","aliases":[],"aliases_ascii":[],"keywords":["cloud","cold","smoking"]},"dizzy":{"unicode":"1f4ab","unicode_alt":"","code_decimal":"💫","name":"dizzy symbol","shortname":":dizzy:","category":"symbols","emoji_order":"1308","aliases":[],"aliases_ascii":[],"keywords":["star","symbol"]},"speech_balloon":{"unicode":"1f4ac","unicode_alt":"","code_decimal":"💬","name":"speech balloon","shortname":":speech_balloon:","category":"symbols","emoji_order":"1309","aliases":[],"aliases_ascii":[],"keywords":["symbol","free speech"]},"speech_left":{"unicode":"1f5e8","unicode_alt":"1f5e8-fe0f","code_decimal":"🗨","name":"left speech bubble","shortname":":speech_left:","category":"symbols","emoji_order":"1310","aliases":[":left_speech_bubble:"],"aliases_ascii":[],"keywords":[]},"anger_right":{"unicode":"1f5ef","unicode_alt":"1f5ef-fe0f","code_decimal":"🗯","name":"right anger bubble","shortname":":anger_right:","category":"symbols","emoji_order":"1311","aliases":[":right_anger_bubble:"],"aliases_ascii":[],"keywords":["symbol"]},"thought_balloon":{"unicode":"1f4ad","unicode_alt":"","code_decimal":"💭","name":"thought balloon","shortname":":thought_balloon:","category":"symbols","emoji_order":"1312","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"hole":{"unicode":"1f573","unicode_alt":"1f573-fe0f","code_decimal":"🕳","name":"hole","shortname":":hole:","category":"objects","emoji_order":"1313","aliases":[],"aliases_ascii":[],"keywords":["object"]},"eyeglasses":{"unicode":"1f453","unicode_alt":"","code_decimal":"👓","name":"eyeglasses","shortname":":eyeglasses:","category":"people","emoji_order":"1314","aliases":[],"aliases_ascii":[],"keywords":["fashion","glasses","accessories"]},"dark_sunglasses":{"unicode":"1f576","unicode_alt":"1f576-fe0f","code_decimal":"🕶","name":"dark sunglasses","shortname":":dark_sunglasses:","category":"people","emoji_order":"1315","aliases":[],"aliases_ascii":[],"keywords":["fashion","glasses","accessories"]},"necktie":{"unicode":"1f454","unicode_alt":"","code_decimal":"👔","name":"necktie","shortname":":necktie:","category":"people","emoji_order":"1316","aliases":[],"aliases_ascii":[],"keywords":["fashion"]},"shirt":{"unicode":"1f455","unicode_alt":"","code_decimal":"👕","name":"t-shirt","shortname":":shirt:","category":"people","emoji_order":"1317","aliases":[],"aliases_ascii":[],"keywords":["fashion"]},"jeans":{"unicode":"1f456","unicode_alt":"","code_decimal":"👖","name":"jeans","shortname":":jeans:","category":"people","emoji_order":"1318","aliases":[],"aliases_ascii":[],"keywords":["fashion"]},"dress":{"unicode":"1f457","unicode_alt":"","code_decimal":"👗","name":"dress","shortname":":dress:","category":"people","emoji_order":"1319","aliases":[],"aliases_ascii":[],"keywords":["women","fashion","sexy","girls night"]},"kimono":{"unicode":"1f458","unicode_alt":"","code_decimal":"👘","name":"kimono","shortname":":kimono:","category":"people","emoji_order":"1320","aliases":[],"aliases_ascii":[],"keywords":["fashion"]},"bikini":{"unicode":"1f459","unicode_alt":"","code_decimal":"👙","name":"bikini","shortname":":bikini:","category":"people","emoji_order":"1321","aliases":[],"aliases_ascii":[],"keywords":["women","fashion","sexy","vacation","tropical","swim"]},"womans_clothes":{"unicode":"1f45a","unicode_alt":"","code_decimal":"👚","name":"womans clothes","shortname":":womans_clothes:","category":"people","emoji_order":"1322","aliases":[],"aliases_ascii":[],"keywords":["women","fashion"]},"purse":{"unicode":"1f45b","unicode_alt":"","code_decimal":"👛","name":"purse","shortname":":purse:","category":"people","emoji_order":"1323","aliases":[],"aliases_ascii":[],"keywords":["bag","women","fashion","accessories","money"]},"handbag":{"unicode":"1f45c","unicode_alt":"","code_decimal":"👜","name":"handbag","shortname":":handbag:","category":"people","emoji_order":"1324","aliases":[],"aliases_ascii":[],"keywords":["bag","women","fashion","vacation","accessories"]},"pouch":{"unicode":"1f45d","unicode_alt":"","code_decimal":"👝","name":"pouch","shortname":":pouch:","category":"people","emoji_order":"1325","aliases":[],"aliases_ascii":[],"keywords":["bag","women","fashion","accessories"]},"shopping_bags":{"unicode":"1f6cd","unicode_alt":"1f6cd-fe0f","code_decimal":"🛍","name":"shopping bags","shortname":":shopping_bags:","category":"objects","emoji_order":"1326","aliases":[],"aliases_ascii":[],"keywords":["object","birthday","parties"]},"school_satchel":{"unicode":"1f392","unicode_alt":"","code_decimal":"🎒","name":"school satchel","shortname":":school_satchel:","category":"people","emoji_order":"1327","aliases":[],"aliases_ascii":[],"keywords":["bag","fashion","office","vacation","accessories"]},"mans_shoe":{"unicode":"1f45e","unicode_alt":"","code_decimal":"👞","name":"mans shoe","shortname":":mans_shoe:","category":"people","emoji_order":"1328","aliases":[],"aliases_ascii":[],"keywords":["fashion","shoe","accessories"]},"athletic_shoe":{"unicode":"1f45f","unicode_alt":"","code_decimal":"👟","name":"athletic shoe","shortname":":athletic_shoe:","category":"people","emoji_order":"1329","aliases":[],"aliases_ascii":[],"keywords":["fashion","shoe","accessories","boys night"]},"high_heel":{"unicode":"1f460","unicode_alt":"","code_decimal":"👠","name":"high-heeled shoe","shortname":":high_heel:","category":"people","emoji_order":"1330","aliases":[],"aliases_ascii":[],"keywords":["women","fashion","shoe","sexy","accessories","girls night"]},"sandal":{"unicode":"1f461","unicode_alt":"","code_decimal":"👡","name":"womans sandal","shortname":":sandal:","category":"people","emoji_order":"1331","aliases":[],"aliases_ascii":[],"keywords":["fashion","shoe","accessories"]},"boot":{"unicode":"1f462","unicode_alt":"","code_decimal":"👢","name":"womans boots","shortname":":boot:","category":"people","emoji_order":"1332","aliases":[],"aliases_ascii":[],"keywords":["women","fashion","shoe","sexy","accessories"]},"crown":{"unicode":"1f451","unicode_alt":"","code_decimal":"👑","name":"crown","shortname":":crown:","category":"people","emoji_order":"1333","aliases":[],"aliases_ascii":[],"keywords":["object","gem","accessories"]},"womans_hat":{"unicode":"1f452","unicode_alt":"","code_decimal":"👒","name":"womans hat","shortname":":womans_hat:","category":"people","emoji_order":"1334","aliases":[],"aliases_ascii":[],"keywords":["women","fashion","accessories"]},"tophat":{"unicode":"1f3a9","unicode_alt":"","code_decimal":"🎩","name":"top hat","shortname":":tophat:","category":"people","emoji_order":"1335","aliases":[],"aliases_ascii":[],"keywords":["hat","fashion","accessories"]},"mortar_board":{"unicode":"1f393","unicode_alt":"","code_decimal":"🎓","name":"graduation cap","shortname":":mortar_board:","category":"people","emoji_order":"1336","aliases":[],"aliases_ascii":[],"keywords":["hat","office","accessories"]},"helmet_with_cross":{"unicode":"26d1","unicode_alt":"26d1-fe0f","code_decimal":"⛑","name":"helmet with white cross","shortname":":helmet_with_cross:","category":"people","emoji_order":"1337","aliases":[":helmet_with_white_cross:"],"aliases_ascii":[],"keywords":["object","hat","accessories","job"]},"prayer_beads":{"unicode":"1f4ff","unicode_alt":"","code_decimal":"📿","name":"prayer beads","shortname":":prayer_beads:","category":"objects","emoji_order":"1338","aliases":[],"aliases_ascii":[],"keywords":["object","rosary"]},"lipstick":{"unicode":"1f484","unicode_alt":"","code_decimal":"💄","name":"lipstick","shortname":":lipstick:","category":"people","emoji_order":"1339","aliases":[],"aliases_ascii":[],"keywords":["object","women","fashion","sexy","lip"]},"ring":{"unicode":"1f48d","unicode_alt":"","code_decimal":"💍","name":"ring","shortname":":ring:","category":"people","emoji_order":"1340","aliases":[],"aliases_ascii":[],"keywords":["wedding","object","fashion","gem","accessories"]},"gem":{"unicode":"1f48e","unicode_alt":"","code_decimal":"💎","name":"gem stone","shortname":":gem:","category":"objects","emoji_order":"1341","aliases":[],"aliases_ascii":[],"keywords":["object","gem"]},"monkey_face":{"unicode":"1f435","unicode_alt":"","code_decimal":"🐵","name":"monkey face","shortname":":monkey_face:","category":"nature","emoji_order":"1342","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"monkey":{"unicode":"1f412","unicode_alt":"","code_decimal":"🐒","name":"monkey","shortname":":monkey:","category":"nature","emoji_order":"1343","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"gorilla":{"unicode":"1f98d","unicode_alt":"","code_decimal":"🦍","name":"gorilla","shortname":":gorilla:","category":"nature","emoji_order":"1344","aliases":[],"aliases_ascii":[],"keywords":[]},"dog":{"unicode":"1f436","unicode_alt":"","code_decimal":"🐶","name":"dog face","shortname":":dog:","category":"nature","emoji_order":"1345","aliases":[],"aliases_ascii":[],"keywords":["dog","pug","animal"]},"dog2":{"unicode":"1f415","unicode_alt":"","code_decimal":"🐕","name":"dog","shortname":":dog2:","category":"nature","emoji_order":"1346","aliases":[],"aliases_ascii":[],"keywords":["dog","pug","animal"]},"poodle":{"unicode":"1f429","unicode_alt":"","code_decimal":"🐩","name":"poodle","shortname":":poodle:","category":"nature","emoji_order":"1347","aliases":[],"aliases_ascii":[],"keywords":["dog","animal"]},"wolf":{"unicode":"1f43a","unicode_alt":"","code_decimal":"🐺","name":"wolf face","shortname":":wolf:","category":"nature","emoji_order":"1348","aliases":[],"aliases_ascii":[],"keywords":["wildlife","roar","animal"]},"fox":{"unicode":"1f98a","unicode_alt":"","code_decimal":"🦊","name":"fox face","shortname":":fox:","category":"nature","emoji_order":"1349","aliases":[":fox_face:"],"aliases_ascii":[],"keywords":[]},"cat":{"unicode":"1f431","unicode_alt":"","code_decimal":"🐱","name":"cat face","shortname":":cat:","category":"nature","emoji_order":"1350","aliases":[],"aliases_ascii":[],"keywords":["halloween","vagina","cat","animal"]},"cat2":{"unicode":"1f408","unicode_alt":"","code_decimal":"🐈","name":"cat","shortname":":cat2:","category":"nature","emoji_order":"1351","aliases":[],"aliases_ascii":[],"keywords":["halloween","cat","animal"]},"lion_face":{"unicode":"1f981","unicode_alt":"","code_decimal":"🦁","name":"lion face","shortname":":lion_face:","category":"nature","emoji_order":"1352","aliases":[":lion:"],"aliases_ascii":[],"keywords":["wildlife","roar","cat","animal"]},"tiger":{"unicode":"1f42f","unicode_alt":"","code_decimal":"🐯","name":"tiger face","shortname":":tiger:","category":"nature","emoji_order":"1353","aliases":[],"aliases_ascii":[],"keywords":["wildlife","roar","cat","animal"]},"tiger2":{"unicode":"1f405","unicode_alt":"","code_decimal":"🐅","name":"tiger","shortname":":tiger2:","category":"nature","emoji_order":"1354","aliases":[],"aliases_ascii":[],"keywords":["wildlife","roar","animal"]},"leopard":{"unicode":"1f406","unicode_alt":"","code_decimal":"🐆","name":"leopard","shortname":":leopard:","category":"nature","emoji_order":"1355","aliases":[],"aliases_ascii":[],"keywords":["wildlife","roar","animal"]},"horse":{"unicode":"1f434","unicode_alt":"","code_decimal":"🐴","name":"horse face","shortname":":horse:","category":"nature","emoji_order":"1356","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"racehorse":{"unicode":"1f40e","unicode_alt":"","code_decimal":"🐎","name":"horse","shortname":":racehorse:","category":"nature","emoji_order":"1357","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"deer":{"unicode":"1f98c","unicode_alt":"","code_decimal":"🦌","name":"deer","shortname":":deer:","category":"nature","emoji_order":"1358","aliases":[],"aliases_ascii":[],"keywords":[]},"unicorn":{"unicode":"1f984","unicode_alt":"","code_decimal":"🦄","name":"unicorn face","shortname":":unicorn:","category":"nature","emoji_order":"1359","aliases":[":unicorn_face:"],"aliases_ascii":[],"keywords":["animal"]},"cow":{"unicode":"1f42e","unicode_alt":"","code_decimal":"🐮","name":"cow face","shortname":":cow:","category":"nature","emoji_order":"1360","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"ox":{"unicode":"1f402","unicode_alt":"","code_decimal":"🐂","name":"ox","shortname":":ox:","category":"nature","emoji_order":"1361","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"water_buffalo":{"unicode":"1f403","unicode_alt":"","code_decimal":"🐃","name":"water buffalo","shortname":":water_buffalo:","category":"nature","emoji_order":"1362","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"cow2":{"unicode":"1f404","unicode_alt":"","code_decimal":"🐄","name":"cow","shortname":":cow2:","category":"nature","emoji_order":"1363","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"pig":{"unicode":"1f437","unicode_alt":"","code_decimal":"🐷","name":"pig face","shortname":":pig:","category":"nature","emoji_order":"1364","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"pig2":{"unicode":"1f416","unicode_alt":"","code_decimal":"🐖","name":"pig","shortname":":pig2:","category":"nature","emoji_order":"1365","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"boar":{"unicode":"1f417","unicode_alt":"","code_decimal":"🐗","name":"boar","shortname":":boar:","category":"nature","emoji_order":"1366","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"pig_nose":{"unicode":"1f43d","unicode_alt":"","code_decimal":"🐽","name":"pig nose","shortname":":pig_nose:","category":"nature","emoji_order":"1367","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"ram":{"unicode":"1f40f","unicode_alt":"","code_decimal":"🐏","name":"ram","shortname":":ram:","category":"nature","emoji_order":"1368","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"sheep":{"unicode":"1f411","unicode_alt":"","code_decimal":"🐑","name":"sheep","shortname":":sheep:","category":"nature","emoji_order":"1369","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"goat":{"unicode":"1f410","unicode_alt":"","code_decimal":"🐐","name":"goat","shortname":":goat:","category":"nature","emoji_order":"1370","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"dromedary_camel":{"unicode":"1f42a","unicode_alt":"","code_decimal":"🐪","name":"dromedary camel","shortname":":dromedary_camel:","category":"nature","emoji_order":"1371","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"camel":{"unicode":"1f42b","unicode_alt":"","code_decimal":"🐫","name":"bactrian camel","shortname":":camel:","category":"nature","emoji_order":"1372","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal","hump day"]},"elephant":{"unicode":"1f418","unicode_alt":"","code_decimal":"🐘","name":"elephant","shortname":":elephant:","category":"nature","emoji_order":"1373","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"rhino":{"unicode":"1f98f","unicode_alt":"","code_decimal":"🦏","name":"rhinoceros","shortname":":rhino:","category":"nature","emoji_order":"1374","aliases":[":rhinoceros:"],"aliases_ascii":[],"keywords":[]},"mouse":{"unicode":"1f42d","unicode_alt":"","code_decimal":"🐭","name":"mouse face","shortname":":mouse:","category":"nature","emoji_order":"1375","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"mouse2":{"unicode":"1f401","unicode_alt":"","code_decimal":"🐁","name":"mouse","shortname":":mouse2:","category":"nature","emoji_order":"1376","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"rat":{"unicode":"1f400","unicode_alt":"","code_decimal":"🐀","name":"rat","shortname":":rat:","category":"nature","emoji_order":"1377","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"hamster":{"unicode":"1f439","unicode_alt":"","code_decimal":"🐹","name":"hamster face","shortname":":hamster:","category":"nature","emoji_order":"1378","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"rabbit":{"unicode":"1f430","unicode_alt":"","code_decimal":"🐰","name":"rabbit face","shortname":":rabbit:","category":"nature","emoji_order":"1379","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"rabbit2":{"unicode":"1f407","unicode_alt":"","code_decimal":"🐇","name":"rabbit","shortname":":rabbit2:","category":"nature","emoji_order":"1380","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"chipmunk":{"unicode":"1f43f","unicode_alt":"1f43f-fe0f","code_decimal":"🐿","name":"chipmunk","shortname":":chipmunk:","category":"nature","emoji_order":"1381","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"bat":{"unicode":"1f987","unicode_alt":"","code_decimal":"🦇","name":"bat","shortname":":bat:","category":"nature","emoji_order":"1382","aliases":[],"aliases_ascii":[],"keywords":[]},"bear":{"unicode":"1f43b","unicode_alt":"","code_decimal":"🐻","name":"bear face","shortname":":bear:","category":"nature","emoji_order":"1383","aliases":[],"aliases_ascii":[],"keywords":["wildlife","roar","animal"]},"koala":{"unicode":"1f428","unicode_alt":"","code_decimal":"🐨","name":"koala","shortname":":koala:","category":"nature","emoji_order":"1384","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"panda_face":{"unicode":"1f43c","unicode_alt":"","code_decimal":"🐼","name":"panda face","shortname":":panda_face:","category":"nature","emoji_order":"1385","aliases":[],"aliases_ascii":[],"keywords":["wildlife","roar","animal"]},"feet":{"unicode":"1f43e","unicode_alt":"","code_decimal":"🐾","name":"paw prints","shortname":":feet:","category":"nature","emoji_order":"1386","aliases":[":paw_prints:"],"aliases_ascii":[],"keywords":["animal"]},"turkey":{"unicode":"1f983","unicode_alt":"","code_decimal":"🦃","name":"turkey","shortname":":turkey:","category":"nature","emoji_order":"1387","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"chicken":{"unicode":"1f414","unicode_alt":"","code_decimal":"🐔","name":"chicken","shortname":":chicken:","category":"nature","emoji_order":"1388","aliases":[],"aliases_ascii":[],"keywords":["animal","chicken"]},"rooster":{"unicode":"1f413","unicode_alt":"","code_decimal":"🐓","name":"rooster","shortname":":rooster:","category":"nature","emoji_order":"1389","aliases":[],"aliases_ascii":[],"keywords":["animal"]},"hatching_chick":{"unicode":"1f423","unicode_alt":"","code_decimal":"🐣","name":"hatching chick","shortname":":hatching_chick:","category":"nature","emoji_order":"1390","aliases":[],"aliases_ascii":[],"keywords":["animal","chicken"]},"baby_chick":{"unicode":"1f424","unicode_alt":"","code_decimal":"🐤","name":"baby chick","shortname":":baby_chick:","category":"nature","emoji_order":"1391","aliases":[],"aliases_ascii":[],"keywords":["animal","chicken"]},"hatched_chick":{"unicode":"1f425","unicode_alt":"","code_decimal":"🐥","name":"front-facing baby chick","shortname":":hatched_chick:","category":"nature","emoji_order":"1392","aliases":[],"aliases_ascii":[],"keywords":["animal","chicken"]},"bird":{"unicode":"1f426","unicode_alt":"","code_decimal":"🐦","name":"bird","shortname":":bird:","category":"nature","emoji_order":"1393","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"penguin":{"unicode":"1f427","unicode_alt":"","code_decimal":"🐧","name":"penguin","shortname":":penguin:","category":"nature","emoji_order":"1394","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"dove":{"unicode":"1f54a","unicode_alt":"1f54a-fe0f","code_decimal":"🕊","name":"dove of peace","shortname":":dove:","category":"nature","emoji_order":"1395","aliases":[":dove_of_peace:"],"aliases_ascii":[],"keywords":["animal"]},"eagle":{"unicode":"1f985","unicode_alt":"","code_decimal":"🦅","name":"eagle","shortname":":eagle:","category":"nature","emoji_order":"1396","aliases":[],"aliases_ascii":[],"keywords":[]},"duck":{"unicode":"1f986","unicode_alt":"","code_decimal":"🦆","name":"duck","shortname":":duck:","category":"nature","emoji_order":"1397","aliases":[],"aliases_ascii":[],"keywords":[]},"owl":{"unicode":"1f989","unicode_alt":"","code_decimal":"🦉","name":"owl","shortname":":owl:","category":"nature","emoji_order":"1398","aliases":[],"aliases_ascii":[],"keywords":[]},"frog":{"unicode":"1f438","unicode_alt":"","code_decimal":"🐸","name":"frog face","shortname":":frog:","category":"nature","emoji_order":"1399","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"crocodile":{"unicode":"1f40a","unicode_alt":"","code_decimal":"🐊","name":"crocodile","shortname":":crocodile:","category":"nature","emoji_order":"1400","aliases":[],"aliases_ascii":[],"keywords":["wildlife","reptile","animal"]},"turtle":{"unicode":"1f422","unicode_alt":"","code_decimal":"🐢","name":"turtle","shortname":":turtle:","category":"nature","emoji_order":"1401","aliases":[],"aliases_ascii":[],"keywords":["wildlife","reptile","animal"]},"lizard":{"unicode":"1f98e","unicode_alt":"","code_decimal":"🦎","name":"lizard","shortname":":lizard:","category":"nature","emoji_order":"1402","aliases":[],"aliases_ascii":[],"keywords":[]},"snake":{"unicode":"1f40d","unicode_alt":"","code_decimal":"🐍","name":"snake","shortname":":snake:","category":"nature","emoji_order":"1403","aliases":[],"aliases_ascii":[],"keywords":["wildlife","reptile","animal","creationism"]},"dragon_face":{"unicode":"1f432","unicode_alt":"","code_decimal":"🐲","name":"dragon face","shortname":":dragon_face:","category":"nature","emoji_order":"1404","aliases":[],"aliases_ascii":[],"keywords":["roar","monster","reptile","animal"]},"dragon":{"unicode":"1f409","unicode_alt":"","code_decimal":"🐉","name":"dragon","shortname":":dragon:","category":"nature","emoji_order":"1405","aliases":[],"aliases_ascii":[],"keywords":["roar","reptile","animal"]},"whale":{"unicode":"1f433","unicode_alt":"","code_decimal":"🐳","name":"spouting whale","shortname":":whale:","category":"nature","emoji_order":"1406","aliases":[],"aliases_ascii":[],"keywords":["wildlife","tropical","whales","animal"]},"whale2":{"unicode":"1f40b","unicode_alt":"","code_decimal":"🐋","name":"whale","shortname":":whale2:","category":"nature","emoji_order":"1407","aliases":[],"aliases_ascii":[],"keywords":["wildlife","tropical","whales","animal"]},"dolphin":{"unicode":"1f42c","unicode_alt":"","code_decimal":"🐬","name":"dolphin","shortname":":dolphin:","category":"nature","emoji_order":"1408","aliases":[],"aliases_ascii":[],"keywords":["wildlife","tropical","animal"]},"fish":{"unicode":"1f41f","unicode_alt":"","code_decimal":"🐟","name":"fish","shortname":":fish:","category":"nature","emoji_order":"1409","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"tropical_fish":{"unicode":"1f420","unicode_alt":"","code_decimal":"🐠","name":"tropical fish","shortname":":tropical_fish:","category":"nature","emoji_order":"1410","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"blowfish":{"unicode":"1f421","unicode_alt":"","code_decimal":"🐡","name":"blowfish","shortname":":blowfish:","category":"nature","emoji_order":"1411","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"shark":{"unicode":"1f988","unicode_alt":"","code_decimal":"🦈","name":"shark","shortname":":shark:","category":"nature","emoji_order":"1412","aliases":[],"aliases_ascii":[],"keywords":[]},"octopus":{"unicode":"1f419","unicode_alt":"","code_decimal":"🐙","name":"octopus","shortname":":octopus:","category":"nature","emoji_order":"1413","aliases":[],"aliases_ascii":[],"keywords":["wildlife","animal"]},"shell":{"unicode":"1f41a","unicode_alt":"","code_decimal":"🐚","name":"spiral shell","shortname":":shell:","category":"nature","emoji_order":"1414","aliases":[],"aliases_ascii":[],"keywords":[]},"crab":{"unicode":"1f980","unicode_alt":"","code_decimal":"🦀","name":"crab","shortname":":crab:","category":"nature","emoji_order":"1415","aliases":[],"aliases_ascii":[],"keywords":["tropical","animal"]},"shrimp":{"unicode":"1f990","unicode_alt":"","code_decimal":"🦐","name":"shrimp","shortname":":shrimp:","category":"nature","emoji_order":"1416","aliases":[],"aliases_ascii":[],"keywords":[]},"squid":{"unicode":"1f991","unicode_alt":"","code_decimal":"🦑","name":"squid","shortname":":squid:","category":"nature","emoji_order":"1417","aliases":[],"aliases_ascii":[],"keywords":[]},"butterfly":{"unicode":"1f98b","unicode_alt":"","code_decimal":"🦋","name":"butterfly","shortname":":butterfly:","category":"nature","emoji_order":"1418","aliases":[],"aliases_ascii":[],"keywords":[]},"snail":{"unicode":"1f40c","unicode_alt":"","code_decimal":"🐌","name":"snail","shortname":":snail:","category":"nature","emoji_order":"1419","aliases":[],"aliases_ascii":[],"keywords":["insects","animal"]},"bug":{"unicode":"1f41b","unicode_alt":"","code_decimal":"🐛","name":"bug","shortname":":bug:","category":"nature","emoji_order":"1420","aliases":[],"aliases_ascii":[],"keywords":["insects","animal"]},"ant":{"unicode":"1f41c","unicode_alt":"","code_decimal":"🐜","name":"ant","shortname":":ant:","category":"nature","emoji_order":"1421","aliases":[],"aliases_ascii":[],"keywords":["insects","animal"]},"bee":{"unicode":"1f41d","unicode_alt":"","code_decimal":"🐝","name":"honeybee","shortname":":bee:","category":"nature","emoji_order":"1422","aliases":[],"aliases_ascii":[],"keywords":["insects","animal"]},"beetle":{"unicode":"1f41e","unicode_alt":"","code_decimal":"🐞","name":"lady beetle","shortname":":beetle:","category":"nature","emoji_order":"1423","aliases":[],"aliases_ascii":[],"keywords":["insects","animal"]},"spider":{"unicode":"1f577","unicode_alt":"1f577-fe0f","code_decimal":"🕷","name":"spider","shortname":":spider:","category":"nature","emoji_order":"1424","aliases":[],"aliases_ascii":[],"keywords":["insects","halloween","animal"]},"spider_web":{"unicode":"1f578","unicode_alt":"1f578-fe0f","code_decimal":"🕸","name":"spider web","shortname":":spider_web:","category":"nature","emoji_order":"1425","aliases":[],"aliases_ascii":[],"keywords":["halloween"]},"scorpion":{"unicode":"1f982","unicode_alt":"","code_decimal":"🦂","name":"scorpion","shortname":":scorpion:","category":"nature","emoji_order":"1426","aliases":[],"aliases_ascii":[],"keywords":["insects","reptile","animal"]},"bouquet":{"unicode":"1f490","unicode_alt":"","code_decimal":"💐","name":"bouquet","shortname":":bouquet:","category":"nature","emoji_order":"1427","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant","rip","condolence"]},"cherry_blossom":{"unicode":"1f338","unicode_alt":"","code_decimal":"🌸","name":"cherry blossom","shortname":":cherry_blossom:","category":"nature","emoji_order":"1428","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant","tropical"]},"white_flower":{"unicode":"1f4ae","unicode_alt":"","code_decimal":"💮","name":"white flower","shortname":":white_flower:","category":"symbols","emoji_order":"1429","aliases":[],"aliases_ascii":[],"keywords":["flower","symbol"]},"rosette":{"unicode":"1f3f5","unicode_alt":"1f3f5-fe0f","code_decimal":"🏵","name":"rosette","shortname":":rosette:","category":"nature","emoji_order":"1430","aliases":[],"aliases_ascii":[],"keywords":["tropical"]},"rose":{"unicode":"1f339","unicode_alt":"","code_decimal":"🌹","name":"rose","shortname":":rose:","category":"nature","emoji_order":"1431","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant","rip","condolence","beautiful"]},"wilted_rose":{"unicode":"1f940","unicode_alt":"","code_decimal":"🥀","name":"wilted flower","shortname":":wilted_rose:","category":"nature","emoji_order":"1432","aliases":[":wilted_flower:"],"aliases_ascii":[],"keywords":[]},"hibiscus":{"unicode":"1f33a","unicode_alt":"","code_decimal":"🌺","name":"hibiscus","shortname":":hibiscus:","category":"nature","emoji_order":"1433","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant","tropical"]},"sunflower":{"unicode":"1f33b","unicode_alt":"","code_decimal":"🌻","name":"sunflower","shortname":":sunflower:","category":"nature","emoji_order":"1434","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant"]},"blossom":{"unicode":"1f33c","unicode_alt":"","code_decimal":"🌼","name":"blossom","shortname":":blossom:","category":"nature","emoji_order":"1435","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant"]},"tulip":{"unicode":"1f337","unicode_alt":"","code_decimal":"🌷","name":"tulip","shortname":":tulip:","category":"nature","emoji_order":"1436","aliases":[],"aliases_ascii":[],"keywords":["nature","flower","plant","vagina","girls night"]},"seedling":{"unicode":"1f331","unicode_alt":"","code_decimal":"🌱","name":"seedling","shortname":":seedling:","category":"nature","emoji_order":"1437","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","leaf"]},"evergreen_tree":{"unicode":"1f332","unicode_alt":"","code_decimal":"🌲","name":"evergreen tree","shortname":":evergreen_tree:","category":"nature","emoji_order":"1438","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","holidays","christmas","camp","trees"]},"deciduous_tree":{"unicode":"1f333","unicode_alt":"","code_decimal":"🌳","name":"deciduous tree","shortname":":deciduous_tree:","category":"nature","emoji_order":"1439","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","camp","trees"]},"palm_tree":{"unicode":"1f334","unicode_alt":"","code_decimal":"🌴","name":"palm tree","shortname":":palm_tree:","category":"nature","emoji_order":"1440","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","tropical","trees"]},"cactus":{"unicode":"1f335","unicode_alt":"","code_decimal":"🌵","name":"cactus","shortname":":cactus:","category":"nature","emoji_order":"1441","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","trees"]},"ear_of_rice":{"unicode":"1f33e","unicode_alt":"","code_decimal":"🌾","name":"ear of rice","shortname":":ear_of_rice:","category":"nature","emoji_order":"1442","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","leaf"]},"herb":{"unicode":"1f33f","unicode_alt":"","code_decimal":"🌿","name":"herb","shortname":":herb:","category":"nature","emoji_order":"1443","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","leaf"]},"shamrock":{"unicode":"2618","unicode_alt":"2618-fe0f","code_decimal":"☘","name":"shamrock","shortname":":shamrock:","category":"nature","emoji_order":"1444","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","luck","leaf"]},"four_leaf_clover":{"unicode":"1f340","unicode_alt":"","code_decimal":"🍀","name":"four leaf clover","shortname":":four_leaf_clover:","category":"nature","emoji_order":"1445","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","luck","leaf","sol"]},"maple_leaf":{"unicode":"1f341","unicode_alt":"","code_decimal":"🍁","name":"maple leaf","shortname":":maple_leaf:","category":"nature","emoji_order":"1446","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","leaf"]},"fallen_leaf":{"unicode":"1f342","unicode_alt":"","code_decimal":"🍂","name":"fallen leaf","shortname":":fallen_leaf:","category":"nature","emoji_order":"1447","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","leaf"]},"leaves":{"unicode":"1f343","unicode_alt":"","code_decimal":"🍃","name":"leaf fluttering in wind","shortname":":leaves:","category":"nature","emoji_order":"1448","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","leaf"]},"grapes":{"unicode":"1f347","unicode_alt":"","code_decimal":"🍇","name":"grapes","shortname":":grapes:","category":"food","emoji_order":"1449","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"melon":{"unicode":"1f348","unicode_alt":"","code_decimal":"🍈","name":"melon","shortname":":melon:","category":"food","emoji_order":"1450","aliases":[],"aliases_ascii":[],"keywords":["fruit","boobs","food"]},"watermelon":{"unicode":"1f349","unicode_alt":"","code_decimal":"🍉","name":"watermelon","shortname":":watermelon:","category":"food","emoji_order":"1451","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"tangerine":{"unicode":"1f34a","unicode_alt":"","code_decimal":"🍊","name":"tangerine","shortname":":tangerine:","category":"food","emoji_order":"1452","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"lemon":{"unicode":"1f34b","unicode_alt":"","code_decimal":"🍋","name":"lemon","shortname":":lemon:","category":"food","emoji_order":"1453","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"banana":{"unicode":"1f34c","unicode_alt":"","code_decimal":"🍌","name":"banana","shortname":":banana:","category":"food","emoji_order":"1454","aliases":[],"aliases_ascii":[],"keywords":["fruit","penis","food"]},"pineapple":{"unicode":"1f34d","unicode_alt":"","code_decimal":"🍍","name":"pineapple","shortname":":pineapple:","category":"food","emoji_order":"1455","aliases":[],"aliases_ascii":[],"keywords":["fruit","food","tropical"]},"apple":{"unicode":"1f34e","unicode_alt":"","code_decimal":"🍎","name":"red apple","shortname":":apple:","category":"food","emoji_order":"1456","aliases":[],"aliases_ascii":[],"keywords":["fruit","food","creationism"]},"green_apple":{"unicode":"1f34f","unicode_alt":"","code_decimal":"🍏","name":"green apple","shortname":":green_apple:","category":"food","emoji_order":"1457","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"pear":{"unicode":"1f350","unicode_alt":"","code_decimal":"🍐","name":"pear","shortname":":pear:","category":"food","emoji_order":"1458","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"peach":{"unicode":"1f351","unicode_alt":"","code_decimal":"🍑","name":"peach","shortname":":peach:","category":"food","emoji_order":"1459","aliases":[],"aliases_ascii":[],"keywords":["fruit","butt","food"]},"cherries":{"unicode":"1f352","unicode_alt":"","code_decimal":"🍒","name":"cherries","shortname":":cherries:","category":"food","emoji_order":"1460","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"strawberry":{"unicode":"1f353","unicode_alt":"","code_decimal":"🍓","name":"strawberry","shortname":":strawberry:","category":"food","emoji_order":"1461","aliases":[],"aliases_ascii":[],"keywords":["fruit","food"]},"kiwi":{"unicode":"1f95d","unicode_alt":"","code_decimal":"🥝","name":"kiwifruit","shortname":":kiwi:","category":"food","emoji_order":"1462","aliases":[":kiwifruit:"],"aliases_ascii":[],"keywords":[]},"tomato":{"unicode":"1f345","unicode_alt":"","code_decimal":"🍅","name":"tomato","shortname":":tomato:","category":"food","emoji_order":"1463","aliases":[],"aliases_ascii":[],"keywords":["fruit","vegetables","food"]},"avocado":{"unicode":"1f951","unicode_alt":"","code_decimal":"🥑","name":"avocado","shortname":":avocado:","category":"food","emoji_order":"1464","aliases":[],"aliases_ascii":[],"keywords":[]},"eggplant":{"unicode":"1f346","unicode_alt":"","code_decimal":"🍆","name":"aubergine","shortname":":eggplant:","category":"food","emoji_order":"1465","aliases":[],"aliases_ascii":[],"keywords":["vegetables","penis","food"]},"potato":{"unicode":"1f954","unicode_alt":"","code_decimal":"🥔","name":"potato","shortname":":potato:","category":"food","emoji_order":"1466","aliases":[],"aliases_ascii":[],"keywords":[]},"carrot":{"unicode":"1f955","unicode_alt":"","code_decimal":"🥕","name":"carrot","shortname":":carrot:","category":"food","emoji_order":"1467","aliases":[],"aliases_ascii":[],"keywords":[]},"corn":{"unicode":"1f33d","unicode_alt":"","code_decimal":"🌽","name":"ear of maize","shortname":":corn:","category":"food","emoji_order":"1468","aliases":[],"aliases_ascii":[],"keywords":["vegetables","food"]},"hot_pepper":{"unicode":"1f336","unicode_alt":"1f336-fe0f","code_decimal":"🌶","name":"hot pepper","shortname":":hot_pepper:","category":"food","emoji_order":"1469","aliases":[],"aliases_ascii":[],"keywords":["vegetables","food"]},"cucumber":{"unicode":"1f952","unicode_alt":"","code_decimal":"🥒","name":"cucumber","shortname":":cucumber:","category":"food","emoji_order":"1470","aliases":[],"aliases_ascii":[],"keywords":[]},"mushroom":{"unicode":"1f344","unicode_alt":"","code_decimal":"🍄","name":"mushroom","shortname":":mushroom:","category":"nature","emoji_order":"1471","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","drugs"]},"peanuts":{"unicode":"1f95c","unicode_alt":"","code_decimal":"🥜","name":"peanuts","shortname":":peanuts:","category":"food","emoji_order":"1472","aliases":[":shelled_peanut:"],"aliases_ascii":[],"keywords":[]},"chestnut":{"unicode":"1f330","unicode_alt":"","code_decimal":"🌰","name":"chestnut","shortname":":chestnut:","category":"nature","emoji_order":"1473","aliases":[],"aliases_ascii":[],"keywords":["nature","plant"]},"bread":{"unicode":"1f35e","unicode_alt":"","code_decimal":"🍞","name":"bread","shortname":":bread:","category":"food","emoji_order":"1474","aliases":[],"aliases_ascii":[],"keywords":["food"]},"croissant":{"unicode":"1f950","unicode_alt":"","code_decimal":"🥐","name":"croissant","shortname":":croissant:","category":"food","emoji_order":"1475","aliases":[],"aliases_ascii":[],"keywords":[]},"french_bread":{"unicode":"1f956","unicode_alt":"","code_decimal":"🥖","name":"baguette bread","shortname":":french_bread:","category":"food","emoji_order":"1476","aliases":[":baguette_bread:"],"aliases_ascii":[],"keywords":[]},"pancakes":{"unicode":"1f95e","unicode_alt":"","code_decimal":"🥞","name":"pancakes","shortname":":pancakes:","category":"food","emoji_order":"1477","aliases":[],"aliases_ascii":[],"keywords":[]},"cheese":{"unicode":"1f9c0","unicode_alt":"","code_decimal":"🧀","name":"cheese wedge","shortname":":cheese:","category":"food","emoji_order":"1478","aliases":[":cheese_wedge:"],"aliases_ascii":[],"keywords":["food"]},"meat_on_bone":{"unicode":"1f356","unicode_alt":"","code_decimal":"🍖","name":"meat on bone","shortname":":meat_on_bone:","category":"food","emoji_order":"1479","aliases":[],"aliases_ascii":[],"keywords":["food"]},"poultry_leg":{"unicode":"1f357","unicode_alt":"","code_decimal":"🍗","name":"poultry leg","shortname":":poultry_leg:","category":"food","emoji_order":"1480","aliases":[],"aliases_ascii":[],"keywords":["food","holidays"]},"bacon":{"unicode":"1f953","unicode_alt":"","code_decimal":"🥓","name":"bacon","shortname":":bacon:","category":"food","emoji_order":"1481","aliases":[],"aliases_ascii":[],"keywords":["pig"]},"hamburger":{"unicode":"1f354","unicode_alt":"","code_decimal":"🍔","name":"hamburger","shortname":":hamburger:","category":"food","emoji_order":"1482","aliases":[],"aliases_ascii":[],"keywords":["america","food"]},"fries":{"unicode":"1f35f","unicode_alt":"","code_decimal":"🍟","name":"french fries","shortname":":fries:","category":"food","emoji_order":"1483","aliases":[],"aliases_ascii":[],"keywords":["america","food"]},"pizza":{"unicode":"1f355","unicode_alt":"","code_decimal":"🍕","name":"slice of pizza","shortname":":pizza:","category":"food","emoji_order":"1484","aliases":[],"aliases_ascii":[],"keywords":["italian","food","boys night"]},"hotdog":{"unicode":"1f32d","unicode_alt":"","code_decimal":"🌭","name":"hot dog","shortname":":hotdog:","category":"food","emoji_order":"1485","aliases":[":hot_dog:"],"aliases_ascii":[],"keywords":["america","food"]},"taco":{"unicode":"1f32e","unicode_alt":"","code_decimal":"🌮","name":"taco","shortname":":taco:","category":"food","emoji_order":"1486","aliases":[],"aliases_ascii":[],"keywords":["food","mexican","vagina"]},"burrito":{"unicode":"1f32f","unicode_alt":"","code_decimal":"🌯","name":"burrito","shortname":":burrito:","category":"food","emoji_order":"1487","aliases":[],"aliases_ascii":[],"keywords":["food","mexican"]},"stuffed_flatbread":{"unicode":"1f959","unicode_alt":"","code_decimal":"🥙","name":"stuffed flatbread","shortname":":stuffed_flatbread:","category":"food","emoji_order":"1488","aliases":[":stuffed_pita:"],"aliases_ascii":[],"keywords":[]},"egg":{"unicode":"1f95a","unicode_alt":"","code_decimal":"🥚","name":"egg","shortname":":egg:","category":"food","emoji_order":"1489","aliases":[],"aliases_ascii":[],"keywords":[]},"cooking":{"unicode":"1f373","unicode_alt":"","code_decimal":"🍳","name":"cooking","shortname":":cooking:","category":"food","emoji_order":"1490","aliases":[],"aliases_ascii":[],"keywords":["food"]},"shallow_pan_of_food":{"unicode":"1f958","unicode_alt":"","code_decimal":"🥘","name":"shallow pan of food","shortname":":shallow_pan_of_food:","category":"food","emoji_order":"1491","aliases":[":paella:"],"aliases_ascii":[],"keywords":["pan of food"]},"stew":{"unicode":"1f372","unicode_alt":"","code_decimal":"🍲","name":"pot of food","shortname":":stew:","category":"food","emoji_order":"1492","aliases":[],"aliases_ascii":[],"keywords":["food","steam"]},"salad":{"unicode":"1f957","unicode_alt":"","code_decimal":"🥗","name":"green salad","shortname":":salad:","category":"food","emoji_order":"1493","aliases":[":green_salad:"],"aliases_ascii":[],"keywords":[]},"popcorn":{"unicode":"1f37f","unicode_alt":"","code_decimal":"🍿","name":"popcorn","shortname":":popcorn:","category":"food","emoji_order":"1494","aliases":[],"aliases_ascii":[],"keywords":["food","parties"]},"bento":{"unicode":"1f371","unicode_alt":"","code_decimal":"🍱","name":"bento box","shortname":":bento:","category":"food","emoji_order":"1495","aliases":[],"aliases_ascii":[],"keywords":["object","sushi","japan","food"]},"rice_cracker":{"unicode":"1f358","unicode_alt":"","code_decimal":"🍘","name":"rice cracker","shortname":":rice_cracker:","category":"food","emoji_order":"1496","aliases":[],"aliases_ascii":[],"keywords":["sushi","food"]},"rice_ball":{"unicode":"1f359","unicode_alt":"","code_decimal":"🍙","name":"rice ball","shortname":":rice_ball:","category":"food","emoji_order":"1497","aliases":[],"aliases_ascii":[],"keywords":["sushi","japan","food"]},"rice":{"unicode":"1f35a","unicode_alt":"","code_decimal":"🍚","name":"cooked rice","shortname":":rice:","category":"food","emoji_order":"1498","aliases":[],"aliases_ascii":[],"keywords":["sushi","japan","food"]},"curry":{"unicode":"1f35b","unicode_alt":"","code_decimal":"🍛","name":"curry and rice","shortname":":curry:","category":"food","emoji_order":"1499","aliases":[],"aliases_ascii":[],"keywords":["food"]},"ramen":{"unicode":"1f35c","unicode_alt":"","code_decimal":"🍜","name":"steaming bowl","shortname":":ramen:","category":"food","emoji_order":"1500","aliases":[],"aliases_ascii":[],"keywords":["noodles","ramen","japan","food"]},"spaghetti":{"unicode":"1f35d","unicode_alt":"","code_decimal":"🍝","name":"spaghetti","shortname":":spaghetti:","category":"food","emoji_order":"1501","aliases":[],"aliases_ascii":[],"keywords":["noodles","pasta","italian","food"]},"sweet_potato":{"unicode":"1f360","unicode_alt":"","code_decimal":"🍠","name":"roasted sweet potato","shortname":":sweet_potato:","category":"food","emoji_order":"1502","aliases":[],"aliases_ascii":[],"keywords":["vegetables","food"]},"oden":{"unicode":"1f362","unicode_alt":"","code_decimal":"🍢","name":"oden","shortname":":oden:","category":"food","emoji_order":"1503","aliases":[],"aliases_ascii":[],"keywords":["food"]},"sushi":{"unicode":"1f363","unicode_alt":"","code_decimal":"🍣","name":"sushi","shortname":":sushi:","category":"food","emoji_order":"1504","aliases":[],"aliases_ascii":[],"keywords":["sushi","japan","food"]},"fried_shrimp":{"unicode":"1f364","unicode_alt":"","code_decimal":"🍤","name":"fried shrimp","shortname":":fried_shrimp:","category":"food","emoji_order":"1505","aliases":[],"aliases_ascii":[],"keywords":["food"]},"fish_cake":{"unicode":"1f365","unicode_alt":"","code_decimal":"🍥","name":"fish cake with swirl design","shortname":":fish_cake:","category":"food","emoji_order":"1506","aliases":[],"aliases_ascii":[],"keywords":["sushi","food"]},"dango":{"unicode":"1f361","unicode_alt":"","code_decimal":"🍡","name":"dango","shortname":":dango:","category":"food","emoji_order":"1507","aliases":[],"aliases_ascii":[],"keywords":["food"]},"icecream":{"unicode":"1f366","unicode_alt":"","code_decimal":"🍦","name":"soft ice cream","shortname":":icecream:","category":"food","emoji_order":"1508","aliases":[],"aliases_ascii":[],"keywords":["food"]},"shaved_ice":{"unicode":"1f367","unicode_alt":"","code_decimal":"🍧","name":"shaved ice","shortname":":shaved_ice:","category":"food","emoji_order":"1509","aliases":[],"aliases_ascii":[],"keywords":["food"]},"ice_cream":{"unicode":"1f368","unicode_alt":"","code_decimal":"🍨","name":"ice cream","shortname":":ice_cream:","category":"food","emoji_order":"1510","aliases":[],"aliases_ascii":[],"keywords":["food"]},"doughnut":{"unicode":"1f369","unicode_alt":"","code_decimal":"🍩","name":"doughnut","shortname":":doughnut:","category":"food","emoji_order":"1511","aliases":[],"aliases_ascii":[],"keywords":["food"]},"cookie":{"unicode":"1f36a","unicode_alt":"","code_decimal":"🍪","name":"cookie","shortname":":cookie:","category":"food","emoji_order":"1512","aliases":[],"aliases_ascii":[],"keywords":["food","vagina"]},"birthday":{"unicode":"1f382","unicode_alt":"","code_decimal":"🎂","name":"birthday cake","shortname":":birthday:","category":"food","emoji_order":"1513","aliases":[],"aliases_ascii":[],"keywords":["birthday","food","parties"]},"cake":{"unicode":"1f370","unicode_alt":"","code_decimal":"🍰","name":"shortcake","shortname":":cake:","category":"food","emoji_order":"1514","aliases":[],"aliases_ascii":[],"keywords":["food"]},"chocolate_bar":{"unicode":"1f36b","unicode_alt":"","code_decimal":"🍫","name":"chocolate bar","shortname":":chocolate_bar:","category":"food","emoji_order":"1515","aliases":[],"aliases_ascii":[],"keywords":["food","halloween"]},"candy":{"unicode":"1f36c","unicode_alt":"","code_decimal":"🍬","name":"candy","shortname":":candy:","category":"food","emoji_order":"1516","aliases":[],"aliases_ascii":[],"keywords":["food","halloween"]},"lollipop":{"unicode":"1f36d","unicode_alt":"","code_decimal":"🍭","name":"lollipop","shortname":":lollipop:","category":"food","emoji_order":"1517","aliases":[],"aliases_ascii":[],"keywords":["food","halloween"]},"custard":{"unicode":"1f36e","unicode_alt":"","code_decimal":"🍮","name":"custard","shortname":":custard:","category":"food","emoji_order":"1518","aliases":[":pudding:",":flan:"],"aliases_ascii":[],"keywords":["food"]},"honey_pot":{"unicode":"1f36f","unicode_alt":"","code_decimal":"🍯","name":"honey pot","shortname":":honey_pot:","category":"food","emoji_order":"1519","aliases":[],"aliases_ascii":[],"keywords":["food","vagina"]},"baby_bottle":{"unicode":"1f37c","unicode_alt":"","code_decimal":"🍼","name":"baby bottle","shortname":":baby_bottle:","category":"food","emoji_order":"1520","aliases":[],"aliases_ascii":[],"keywords":["drink","object","food","baby"]},"milk":{"unicode":"1f95b","unicode_alt":"","code_decimal":"🥛","name":"glass of milk","shortname":":milk:","category":"food","emoji_order":"1521","aliases":[":glass_of_milk:"],"aliases_ascii":[],"keywords":[]},"coffee":{"unicode":"2615","unicode_alt":"2615-fe0f","code_decimal":"☕","name":"hot beverage","shortname":":coffee:","category":"food","emoji_order":"1522","aliases":[],"aliases_ascii":[],"keywords":["drink","caffeine","steam","morning"]},"tea":{"unicode":"1f375","unicode_alt":"","code_decimal":"🍵","name":"teacup without handle","shortname":":tea:","category":"food","emoji_order":"1523","aliases":[],"aliases_ascii":[],"keywords":["drink","japan","caffeine","steam","morning"]},"sake":{"unicode":"1f376","unicode_alt":"","code_decimal":"🍶","name":"sake bottle and cup","shortname":":sake:","category":"food","emoji_order":"1524","aliases":[],"aliases_ascii":[],"keywords":["drink","japan","sake","alcohol","girls night"]},"champagne":{"unicode":"1f37e","unicode_alt":"","code_decimal":"🍾","name":"bottle with popping cork","shortname":":champagne:","category":"food","emoji_order":"1525","aliases":[":bottle_with_popping_cork:"],"aliases_ascii":[],"keywords":["drink","cheers","alcohol","parties"]},"wine_glass":{"unicode":"1f377","unicode_alt":"","code_decimal":"🍷","name":"wine glass","shortname":":wine_glass:","category":"food","emoji_order":"1526","aliases":[],"aliases_ascii":[],"keywords":["drink","italian","alcohol","girls night","parties"]},"cocktail":{"unicode":"1f378","unicode_alt":"","code_decimal":"🍸","name":"cocktail glass","shortname":":cocktail:","category":"food","emoji_order":"1527","aliases":[],"aliases_ascii":[],"keywords":["drink","cocktail","alcohol","girls night","parties"]},"tropical_drink":{"unicode":"1f379","unicode_alt":"","code_decimal":"🍹","name":"tropical drink","shortname":":tropical_drink:","category":"food","emoji_order":"1528","aliases":[],"aliases_ascii":[],"keywords":["drink","cocktail","tropical","alcohol"]},"beer":{"unicode":"1f37a","unicode_alt":"","code_decimal":"🍺","name":"beer mug","shortname":":beer:","category":"food","emoji_order":"1529","aliases":[],"aliases_ascii":[],"keywords":["drink","beer","alcohol","parties"]},"beers":{"unicode":"1f37b","unicode_alt":"","code_decimal":"🍻","name":"clinking beer mugs","shortname":":beers:","category":"food","emoji_order":"1530","aliases":[],"aliases_ascii":[],"keywords":["drink","cheers","beer","alcohol","thank you","boys night","parties"]},"champagne_glass":{"unicode":"1f942","unicode_alt":"","code_decimal":"🥂","name":"clinking glasses","shortname":":champagne_glass:","category":"food","emoji_order":"1531","aliases":[":clinking_glass:"],"aliases_ascii":[],"keywords":[]},"tumbler_glass":{"unicode":"1f943","unicode_alt":"","code_decimal":"🥃","name":"tumbler glass","shortname":":tumbler_glass:","category":"food","emoji_order":"1532","aliases":[":whisky:"],"aliases_ascii":[],"keywords":["booze"]},"fork_knife_plate":{"unicode":"1f37d","unicode_alt":"1f37d-fe0f","code_decimal":"🍽","name":"fork and knife with plate","shortname":":fork_knife_plate:","category":"food","emoji_order":"1533","aliases":[":fork_and_knife_with_plate:"],"aliases_ascii":[],"keywords":["object","food"]},"fork_and_knife":{"unicode":"1f374","unicode_alt":"","code_decimal":"🍴","name":"fork and knife","shortname":":fork_and_knife:","category":"food","emoji_order":"1534","aliases":[],"aliases_ascii":[],"keywords":["object","weapon","food"]},"spoon":{"unicode":"1f944","unicode_alt":"","code_decimal":"🥄","name":"spoon","shortname":":spoon:","category":"food","emoji_order":"1535","aliases":[],"aliases_ascii":[],"keywords":[]},"knife":{"unicode":"1f52a","unicode_alt":"","code_decimal":"🔪","name":"hocho","shortname":":knife:","category":"objects","emoji_order":"1536","aliases":[],"aliases_ascii":[],"keywords":["object","weapon"]},"amphora":{"unicode":"1f3fa","unicode_alt":"","code_decimal":"🏺","name":"amphora","shortname":":amphora:","category":"objects","emoji_order":"1537","aliases":[],"aliases_ascii":[],"keywords":["object"]},"earth_africa":{"unicode":"1f30d","unicode_alt":"","code_decimal":"🌍","name":"earth globe europe-africa","shortname":":earth_africa:","category":"nature","emoji_order":"1538","aliases":[],"aliases_ascii":[],"keywords":["map","vacation","globe"]},"earth_americas":{"unicode":"1f30e","unicode_alt":"","code_decimal":"🌎","name":"earth globe americas","shortname":":earth_americas:","category":"nature","emoji_order":"1539","aliases":[],"aliases_ascii":[],"keywords":["map","vacation","globe"]},"earth_asia":{"unicode":"1f30f","unicode_alt":"","code_decimal":"🌏","name":"earth globe asia-australia","shortname":":earth_asia:","category":"nature","emoji_order":"1540","aliases":[],"aliases_ascii":[],"keywords":["map","vacation","globe"]},"globe_with_meridians":{"unicode":"1f310","unicode_alt":"","code_decimal":"🌐","name":"globe with meridians","shortname":":globe_with_meridians:","category":"symbols","emoji_order":"1541","aliases":[],"aliases_ascii":[],"keywords":["symbol","globe"]},"map":{"unicode":"1f5fa","unicode_alt":"1f5fa-fe0f","code_decimal":"🗺","name":"world map","shortname":":map:","category":"objects","emoji_order":"1542","aliases":[":world_map:"],"aliases_ascii":[],"keywords":["travel","map","vacation"]},"japan":{"unicode":"1f5fe","unicode_alt":"","code_decimal":"🗾","name":"silhouette of japan","shortname":":japan:","category":"travel","emoji_order":"1543","aliases":[],"aliases_ascii":[],"keywords":["places","travel","map","vacation","tropical"]},"mountain_snow":{"unicode":"1f3d4","unicode_alt":"1f3d4-fe0f","code_decimal":"🏔","name":"snow capped mountain","shortname":":mountain_snow:","category":"travel","emoji_order":"1544","aliases":[":snow_capped_mountain:"],"aliases_ascii":[],"keywords":["places","travel","vacation","cold","camp"]},"mountain":{"unicode":"26f0","unicode_alt":"26f0-fe0f","code_decimal":"⛰","name":"mountain","shortname":":mountain:","category":"travel","emoji_order":"1545","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","camp"]},"volcano":{"unicode":"1f30b","unicode_alt":"","code_decimal":"🌋","name":"volcano","shortname":":volcano:","category":"travel","emoji_order":"1546","aliases":[],"aliases_ascii":[],"keywords":["places","tropical"]},"mount_fuji":{"unicode":"1f5fb","unicode_alt":"","code_decimal":"🗻","name":"mount fuji","shortname":":mount_fuji:","category":"travel","emoji_order":"1547","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","cold","camp"]},"camping":{"unicode":"1f3d5","unicode_alt":"1f3d5-fe0f","code_decimal":"🏕","name":"camping","shortname":":camping:","category":"travel","emoji_order":"1548","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","camp"]},"beach":{"unicode":"1f3d6","unicode_alt":"1f3d6-fe0f","code_decimal":"🏖","name":"beach with umbrella","shortname":":beach:","category":"travel","emoji_order":"1549","aliases":[":beach_with_umbrella:"],"aliases_ascii":[],"keywords":["places","travel","vacation","tropical","beach","swim"]},"desert":{"unicode":"1f3dc","unicode_alt":"1f3dc-fe0f","code_decimal":"🏜","name":"desert","shortname":":desert:","category":"travel","emoji_order":"1550","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","hot"]},"island":{"unicode":"1f3dd","unicode_alt":"1f3dd-fe0f","code_decimal":"🏝","name":"desert island","shortname":":island:","category":"travel","emoji_order":"1551","aliases":[":desert_island:"],"aliases_ascii":[],"keywords":["places","travel","vacation","tropical","beach","swim"]},"park":{"unicode":"1f3de","unicode_alt":"1f3de-fe0f","code_decimal":"🏞","name":"national park","shortname":":park:","category":"travel","emoji_order":"1552","aliases":[":national_park:"],"aliases_ascii":[],"keywords":["travel","vacation","park","camp"]},"stadium":{"unicode":"1f3df","unicode_alt":"1f3df-fe0f","code_decimal":"🏟","name":"stadium","shortname":":stadium:","category":"travel","emoji_order":"1553","aliases":[],"aliases_ascii":[],"keywords":["places","building","travel","vacation","boys night"]},"classical_building":{"unicode":"1f3db","unicode_alt":"1f3db-fe0f","code_decimal":"🏛","name":"classical building","shortname":":classical_building:","category":"travel","emoji_order":"1554","aliases":[],"aliases_ascii":[],"keywords":["places","building","travel","vacation"]},"construction_site":{"unicode":"1f3d7","unicode_alt":"1f3d7-fe0f","code_decimal":"🏗","name":"building construction","shortname":":construction_site:","category":"travel","emoji_order":"1555","aliases":[":building_construction:"],"aliases_ascii":[],"keywords":["building","crane"]},"homes":{"unicode":"1f3d8","unicode_alt":"1f3d8-fe0f","code_decimal":"🏘","name":"house buildings","shortname":":homes:","category":"travel","emoji_order":"1556","aliases":[":house_buildings:"],"aliases_ascii":[],"keywords":["places","building","house"]},"cityscape":{"unicode":"1f3d9","unicode_alt":"1f3d9-fe0f","code_decimal":"🏙","name":"cityscape","shortname":":cityscape:","category":"travel","emoji_order":"1557","aliases":[],"aliases_ascii":[],"keywords":["places","building","vacation"]},"house_abandoned":{"unicode":"1f3da","unicode_alt":"1f3da-fe0f","code_decimal":"🏚","name":"derelict house building","shortname":":house_abandoned:","category":"travel","emoji_order":"1558","aliases":[":derelict_house_building:"],"aliases_ascii":[],"keywords":["places","building","house"]},"house":{"unicode":"1f3e0","unicode_alt":"","code_decimal":"🏠","name":"house building","shortname":":house:","category":"travel","emoji_order":"1559","aliases":[],"aliases_ascii":[],"keywords":["places","building","house"]},"house_with_garden":{"unicode":"1f3e1","unicode_alt":"","code_decimal":"🏡","name":"house with garden","shortname":":house_with_garden:","category":"travel","emoji_order":"1560","aliases":[],"aliases_ascii":[],"keywords":["places","building","house"]},"office":{"unicode":"1f3e2","unicode_alt":"","code_decimal":"🏢","name":"office building","shortname":":office:","category":"travel","emoji_order":"1561","aliases":[],"aliases_ascii":[],"keywords":["places","building","work"]},"post_office":{"unicode":"1f3e3","unicode_alt":"","code_decimal":"🏣","name":"japanese post office","shortname":":post_office:","category":"travel","emoji_order":"1562","aliases":[],"aliases_ascii":[],"keywords":["places","building","post office"]},"european_post_office":{"unicode":"1f3e4","unicode_alt":"","code_decimal":"🏤","name":"european post office","shortname":":european_post_office:","category":"travel","emoji_order":"1563","aliases":[],"aliases_ascii":[],"keywords":["places","building","post office"]},"hospital":{"unicode":"1f3e5","unicode_alt":"","code_decimal":"🏥","name":"hospital","shortname":":hospital:","category":"travel","emoji_order":"1564","aliases":[],"aliases_ascii":[],"keywords":["places","building","health","911"]},"bank":{"unicode":"1f3e6","unicode_alt":"","code_decimal":"🏦","name":"bank","shortname":":bank:","category":"travel","emoji_order":"1565","aliases":[],"aliases_ascii":[],"keywords":["places","building"]},"hotel":{"unicode":"1f3e8","unicode_alt":"","code_decimal":"🏨","name":"hotel","shortname":":hotel:","category":"travel","emoji_order":"1566","aliases":[],"aliases_ascii":[],"keywords":["places","building","vacation"]},"love_hotel":{"unicode":"1f3e9","unicode_alt":"","code_decimal":"🏩","name":"love hotel","shortname":":love_hotel:","category":"travel","emoji_order":"1567","aliases":[],"aliases_ascii":[],"keywords":["places","building","love"]},"convenience_store":{"unicode":"1f3ea","unicode_alt":"","code_decimal":"🏪","name":"convenience store","shortname":":convenience_store:","category":"travel","emoji_order":"1568","aliases":[],"aliases_ascii":[],"keywords":["places","building"]},"school":{"unicode":"1f3eb","unicode_alt":"","code_decimal":"🏫","name":"school","shortname":":school:","category":"travel","emoji_order":"1569","aliases":[],"aliases_ascii":[],"keywords":["places","building"]},"department_store":{"unicode":"1f3ec","unicode_alt":"","code_decimal":"🏬","name":"department store","shortname":":department_store:","category":"travel","emoji_order":"1570","aliases":[],"aliases_ascii":[],"keywords":["places","building"]},"factory":{"unicode":"1f3ed","unicode_alt":"","code_decimal":"🏭","name":"factory","shortname":":factory:","category":"travel","emoji_order":"1571","aliases":[],"aliases_ascii":[],"keywords":["places","building","travel","steam"]},"japanese_castle":{"unicode":"1f3ef","unicode_alt":"","code_decimal":"🏯","name":"japanese castle","shortname":":japanese_castle:","category":"travel","emoji_order":"1572","aliases":[],"aliases_ascii":[],"keywords":["places","building","travel","vacation"]},"european_castle":{"unicode":"1f3f0","unicode_alt":"","code_decimal":"🏰","name":"european castle","shortname":":european_castle:","category":"travel","emoji_order":"1573","aliases":[],"aliases_ascii":[],"keywords":["places","building","travel","vacation"]},"wedding":{"unicode":"1f492","unicode_alt":"","code_decimal":"💒","name":"wedding","shortname":":wedding:","category":"travel","emoji_order":"1574","aliases":[],"aliases_ascii":[],"keywords":["places","wedding","building","love","parties"]},"tokyo_tower":{"unicode":"1f5fc","unicode_alt":"","code_decimal":"🗼","name":"tokyo tower","shortname":":tokyo_tower:","category":"travel","emoji_order":"1575","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","eiffel tower"]},"statue_of_liberty":{"unicode":"1f5fd","unicode_alt":"","code_decimal":"🗽","name":"statue of liberty","shortname":":statue_of_liberty:","category":"travel","emoji_order":"1576","aliases":[],"aliases_ascii":[],"keywords":["places","america","travel","vacation","statue of liberty","free speech"]},"church":{"unicode":"26ea","unicode_alt":"26ea-fe0f","code_decimal":"⛪","name":"church","shortname":":church:","category":"travel","emoji_order":"1577","aliases":[],"aliases_ascii":[],"keywords":["places","wedding","religion","building","condolence"]},"mosque":{"unicode":"1f54c","unicode_alt":"","code_decimal":"🕌","name":"mosque","shortname":":mosque:","category":"travel","emoji_order":"1578","aliases":[],"aliases_ascii":[],"keywords":["places","religion","building","vacation","condolence"]},"synagogue":{"unicode":"1f54d","unicode_alt":"","code_decimal":"🕍","name":"synagogue","shortname":":synagogue:","category":"travel","emoji_order":"1579","aliases":[],"aliases_ascii":[],"keywords":["places","religion","building","travel","vacation","condolence"]},"shinto_shrine":{"unicode":"26e9","unicode_alt":"26e9-fe0f","code_decimal":"⛩","name":"shinto shrine","shortname":":shinto_shrine:","category":"travel","emoji_order":"1580","aliases":[],"aliases_ascii":[],"keywords":["places","building","travel","vacation"]},"kaaba":{"unicode":"1f54b","unicode_alt":"","code_decimal":"🕋","name":"kaaba","shortname":":kaaba:","category":"travel","emoji_order":"1581","aliases":[],"aliases_ascii":[],"keywords":["places","religion","building","condolence"]},"fountain":{"unicode":"26f2","unicode_alt":"26f2-fe0f","code_decimal":"⛲","name":"fountain","shortname":":fountain:","category":"travel","emoji_order":"1582","aliases":[],"aliases_ascii":[],"keywords":["travel","vacation"]},"tent":{"unicode":"26fa","unicode_alt":"26fa-fe0f","code_decimal":"⛺","name":"tent","shortname":":tent:","category":"travel","emoji_order":"1583","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","camp"]},"foggy":{"unicode":"1f301","unicode_alt":"","code_decimal":"🌁","name":"foggy","shortname":":foggy:","category":"travel","emoji_order":"1584","aliases":[],"aliases_ascii":[],"keywords":["places","building","sky","travel","vacation"]},"night_with_stars":{"unicode":"1f303","unicode_alt":"","code_decimal":"🌃","name":"night with stars","shortname":":night_with_stars:","category":"travel","emoji_order":"1585","aliases":[],"aliases_ascii":[],"keywords":["places","building","sky","vacation","goodnight"]},"sunrise_over_mountains":{"unicode":"1f304","unicode_alt":"","code_decimal":"🌄","name":"sunrise over mountains","shortname":":sunrise_over_mountains:","category":"travel","emoji_order":"1586","aliases":[],"aliases_ascii":[],"keywords":["places","sky","travel","vacation","day","sun","camp","morning"]},"sunrise":{"unicode":"1f305","unicode_alt":"","code_decimal":"🌅","name":"sunrise","shortname":":sunrise:","category":"travel","emoji_order":"1587","aliases":[],"aliases_ascii":[],"keywords":["places","sky","travel","vacation","tropical","day","sun","hump day","morning"]},"city_dusk":{"unicode":"1f306","unicode_alt":"","code_decimal":"🌆","name":"cityscape at dusk","shortname":":city_dusk:","category":"travel","emoji_order":"1588","aliases":[],"aliases_ascii":[],"keywords":["places","building"]},"city_sunset":{"unicode":"1f307","unicode_alt":"","code_decimal":"🌇","name":"sunset over buildings","shortname":":city_sunset:","category":"travel","emoji_order":"1589","aliases":[":city_sunrise:"],"aliases_ascii":[],"keywords":["places","building","sky","vacation"]},"bridge_at_night":{"unicode":"1f309","unicode_alt":"","code_decimal":"🌉","name":"bridge at night","shortname":":bridge_at_night:","category":"travel","emoji_order":"1590","aliases":[],"aliases_ascii":[],"keywords":["places","travel","vacation","goodnight"]},"hotsprings":{"unicode":"2668","unicode_alt":"2668-fe0f","code_decimal":"♨","name":"hot springs","shortname":":hotsprings:","category":"symbols","emoji_order":"1591","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"milky_way":{"unicode":"1f30c","unicode_alt":"","code_decimal":"🌌","name":"milky way","shortname":":milky_way:","category":"travel","emoji_order":"1592","aliases":[],"aliases_ascii":[],"keywords":["places","space","sky","travel","vacation"]},"carousel_horse":{"unicode":"1f3a0","unicode_alt":"","code_decimal":"🎠","name":"carousel horse","shortname":":carousel_horse:","category":"travel","emoji_order":"1593","aliases":[],"aliases_ascii":[],"keywords":["places","object","vacation","roller coaster","carousel"]},"ferris_wheel":{"unicode":"1f3a1","unicode_alt":"","code_decimal":"🎡","name":"ferris wheel","shortname":":ferris_wheel:","category":"travel","emoji_order":"1594","aliases":[],"aliases_ascii":[],"keywords":["places","vacation","ferris wheel"]},"roller_coaster":{"unicode":"1f3a2","unicode_alt":"","code_decimal":"🎢","name":"roller coaster","shortname":":roller_coaster:","category":"travel","emoji_order":"1595","aliases":[],"aliases_ascii":[],"keywords":["places","vacation","roller coaster"]},"barber":{"unicode":"1f488","unicode_alt":"","code_decimal":"💈","name":"barber pole","shortname":":barber:","category":"objects","emoji_order":"1596","aliases":[],"aliases_ascii":[],"keywords":["object"]},"circus_tent":{"unicode":"1f3aa","unicode_alt":"","code_decimal":"🎪","name":"circus tent","shortname":":circus_tent:","category":"activity","emoji_order":"1597","aliases":[],"aliases_ascii":[],"keywords":["circus tent"]},"performing_arts":{"unicode":"1f3ad","unicode_alt":"","code_decimal":"🎭","name":"performing arts","shortname":":performing_arts:","category":"activity","emoji_order":"1598","aliases":[],"aliases_ascii":[],"keywords":["theatre","movie"]},"frame_photo":{"unicode":"1f5bc","unicode_alt":"1f5bc-fe0f","code_decimal":"🖼","name":"frame with picture","shortname":":frame_photo:","category":"objects","emoji_order":"1599","aliases":[":frame_with_picture:"],"aliases_ascii":[],"keywords":["travel","vacation"]},"art":{"unicode":"1f3a8","unicode_alt":"","code_decimal":"🎨","name":"artist palette","shortname":":art:","category":"activity","emoji_order":"1600","aliases":[],"aliases_ascii":[],"keywords":[]},"slot_machine":{"unicode":"1f3b0","unicode_alt":"","code_decimal":"🎰","name":"slot machine","shortname":":slot_machine:","category":"activity","emoji_order":"1601","aliases":[],"aliases_ascii":[],"keywords":["game","boys night"]},"steam_locomotive":{"unicode":"1f682","unicode_alt":"","code_decimal":"🚂","name":"steam locomotive","shortname":":steam_locomotive:","category":"travel","emoji_order":"1602","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train","steam"]},"railway_car":{"unicode":"1f683","unicode_alt":"","code_decimal":"🚃","name":"railway car","shortname":":railway_car:","category":"travel","emoji_order":"1603","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"bullettrain_side":{"unicode":"1f684","unicode_alt":"","code_decimal":"🚄","name":"high-speed train","shortname":":bullettrain_side:","category":"travel","emoji_order":"1604","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"bullettrain_front":{"unicode":"1f685","unicode_alt":"","code_decimal":"🚅","name":"high-speed train with bullet nose","shortname":":bullettrain_front:","category":"travel","emoji_order":"1605","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"train2":{"unicode":"1f686","unicode_alt":"","code_decimal":"🚆","name":"train","shortname":":train2:","category":"travel","emoji_order":"1606","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"metro":{"unicode":"1f687","unicode_alt":"","code_decimal":"🚇","name":"metro","shortname":":metro:","category":"travel","emoji_order":"1607","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"light_rail":{"unicode":"1f688","unicode_alt":"","code_decimal":"🚈","name":"light rail","shortname":":light_rail:","category":"travel","emoji_order":"1608","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"station":{"unicode":"1f689","unicode_alt":"","code_decimal":"🚉","name":"station","shortname":":station:","category":"travel","emoji_order":"1609","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"tram":{"unicode":"1f68a","unicode_alt":"","code_decimal":"🚊","name":"tram","shortname":":tram:","category":"travel","emoji_order":"1610","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"monorail":{"unicode":"1f69d","unicode_alt":"","code_decimal":"🚝","name":"monorail","shortname":":monorail:","category":"travel","emoji_order":"1611","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train","vacation"]},"mountain_railway":{"unicode":"1f69e","unicode_alt":"","code_decimal":"🚞","name":"mountain railway","shortname":":mountain_railway:","category":"travel","emoji_order":"1612","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"train":{"unicode":"1f68b","unicode_alt":"","code_decimal":"🚋","name":"tram car","shortname":":train:","category":"travel","emoji_order":"1613","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"bus":{"unicode":"1f68c","unicode_alt":"","code_decimal":"🚌","name":"bus","shortname":":bus:","category":"travel","emoji_order":"1614","aliases":[],"aliases_ascii":[],"keywords":["transportation","bus","office"]},"oncoming_bus":{"unicode":"1f68d","unicode_alt":"","code_decimal":"🚍","name":"oncoming bus","shortname":":oncoming_bus:","category":"travel","emoji_order":"1615","aliases":[],"aliases_ascii":[],"keywords":["transportation","bus","travel"]},"trolleybus":{"unicode":"1f68e","unicode_alt":"","code_decimal":"🚎","name":"trolleybus","shortname":":trolleybus:","category":"travel","emoji_order":"1616","aliases":[],"aliases_ascii":[],"keywords":["transportation","bus","travel"]},"minibus":{"unicode":"1f690","unicode_alt":"","code_decimal":"🚐","name":"minibus","shortname":":minibus:","category":"travel","emoji_order":"1617","aliases":[],"aliases_ascii":[],"keywords":["transportation","bus"]},"ambulance":{"unicode":"1f691","unicode_alt":"","code_decimal":"🚑","name":"ambulance","shortname":":ambulance:","category":"travel","emoji_order":"1618","aliases":[],"aliases_ascii":[],"keywords":["transportation","911"]},"fire_engine":{"unicode":"1f692","unicode_alt":"","code_decimal":"🚒","name":"fire engine","shortname":":fire_engine:","category":"travel","emoji_order":"1619","aliases":[],"aliases_ascii":[],"keywords":["transportation","truck","911"]},"police_car":{"unicode":"1f693","unicode_alt":"","code_decimal":"🚓","name":"police car","shortname":":police_car:","category":"travel","emoji_order":"1620","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","police","911"]},"oncoming_police_car":{"unicode":"1f694","unicode_alt":"","code_decimal":"🚔","name":"oncoming police car","shortname":":oncoming_police_car:","category":"travel","emoji_order":"1621","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","police","911"]},"taxi":{"unicode":"1f695","unicode_alt":"","code_decimal":"🚕","name":"taxi","shortname":":taxi:","category":"travel","emoji_order":"1622","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","travel"]},"oncoming_taxi":{"unicode":"1f696","unicode_alt":"","code_decimal":"🚖","name":"oncoming taxi","shortname":":oncoming_taxi:","category":"travel","emoji_order":"1623","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","travel"]},"red_car":{"unicode":"1f697","unicode_alt":"","code_decimal":"🚗","name":"automobile","shortname":":red_car:","category":"travel","emoji_order":"1624","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","travel"]},"oncoming_automobile":{"unicode":"1f698","unicode_alt":"","code_decimal":"🚘","name":"oncoming automobile","shortname":":oncoming_automobile:","category":"travel","emoji_order":"1625","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","travel"]},"blue_car":{"unicode":"1f699","unicode_alt":"","code_decimal":"🚙","name":"recreational vehicle","shortname":":blue_car:","category":"travel","emoji_order":"1626","aliases":[],"aliases_ascii":[],"keywords":["transportation","car","travel"]},"truck":{"unicode":"1f69a","unicode_alt":"","code_decimal":"🚚","name":"delivery truck","shortname":":truck:","category":"travel","emoji_order":"1627","aliases":[],"aliases_ascii":[],"keywords":["transportation","truck"]},"articulated_lorry":{"unicode":"1f69b","unicode_alt":"","code_decimal":"🚛","name":"articulated lorry","shortname":":articulated_lorry:","category":"travel","emoji_order":"1628","aliases":[],"aliases_ascii":[],"keywords":["transportation","truck"]},"tractor":{"unicode":"1f69c","unicode_alt":"","code_decimal":"🚜","name":"tractor","shortname":":tractor:","category":"travel","emoji_order":"1629","aliases":[],"aliases_ascii":[],"keywords":["transportation"]},"bike":{"unicode":"1f6b2","unicode_alt":"","code_decimal":"🚲","name":"bicycle","shortname":":bike:","category":"travel","emoji_order":"1630","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","bike"]},"scooter":{"unicode":"1f6f4","unicode_alt":"","code_decimal":"🛴","name":"scooter","shortname":":scooter:","category":"travel","emoji_order":"1631","aliases":[],"aliases_ascii":[],"keywords":[]},"motor_scooter":{"unicode":"1f6f5","unicode_alt":"","code_decimal":"🛵","name":"motor scooter","shortname":":motor_scooter:","category":"travel","emoji_order":"1632","aliases":[":motorbike:"],"aliases_ascii":[],"keywords":["moped"]},"busstop":{"unicode":"1f68f","unicode_alt":"","code_decimal":"🚏","name":"bus stop","shortname":":busstop:","category":"travel","emoji_order":"1633","aliases":[],"aliases_ascii":[],"keywords":["object"]},"motorway":{"unicode":"1f6e3","unicode_alt":"1f6e3-fe0f","code_decimal":"🛣","name":"motorway","shortname":":motorway:","category":"travel","emoji_order":"1634","aliases":[],"aliases_ascii":[],"keywords":["travel","vacation","camp"]},"railway_track":{"unicode":"1f6e4","unicode_alt":"1f6e4-fe0f","code_decimal":"🛤","name":"railway track","shortname":":railway_track:","category":"travel","emoji_order":"1635","aliases":[":railroad_track:"],"aliases_ascii":[],"keywords":["travel","train","vacation"]},"fuelpump":{"unicode":"26fd","unicode_alt":"26fd-fe0f","code_decimal":"⛽","name":"fuel pump","shortname":":fuelpump:","category":"travel","emoji_order":"1636","aliases":[],"aliases_ascii":[],"keywords":["object","gas pump"]},"rotating_light":{"unicode":"1f6a8","unicode_alt":"","code_decimal":"🚨","name":"police cars revolving light","shortname":":rotating_light:","category":"travel","emoji_order":"1637","aliases":[],"aliases_ascii":[],"keywords":["transportation","object","police","911"]},"traffic_light":{"unicode":"1f6a5","unicode_alt":"","code_decimal":"🚥","name":"horizontal traffic light","shortname":":traffic_light:","category":"travel","emoji_order":"1638","aliases":[],"aliases_ascii":[],"keywords":["object","stop light"]},"vertical_traffic_light":{"unicode":"1f6a6","unicode_alt":"","code_decimal":"🚦","name":"vertical traffic light","shortname":":vertical_traffic_light:","category":"travel","emoji_order":"1639","aliases":[],"aliases_ascii":[],"keywords":["object","stop light"]},"construction":{"unicode":"1f6a7","unicode_alt":"","code_decimal":"🚧","name":"construction sign","shortname":":construction:","category":"travel","emoji_order":"1640","aliases":[],"aliases_ascii":[],"keywords":["object"]},"octagonal_sign":{"unicode":"1f6d1","unicode_alt":"","code_decimal":"🛑","name":"octagonal sign","shortname":":octagonal_sign:","category":"symbols","emoji_order":"1641","aliases":[":stop_sign:"],"aliases_ascii":[],"keywords":[]},"anchor":{"unicode":"2693","unicode_alt":"2693-fe0f","code_decimal":"⚓","name":"anchor","shortname":":anchor:","category":"travel","emoji_order":"1642","aliases":[],"aliases_ascii":[],"keywords":["object","travel","boat","vacation"]},"sailboat":{"unicode":"26f5","unicode_alt":"26f5-fe0f","code_decimal":"⛵","name":"sailboat","shortname":":sailboat:","category":"travel","emoji_order":"1643","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","boat","vacation"]},"canoe":{"unicode":"1f6f6","unicode_alt":"","code_decimal":"🛶","name":"canoe","shortname":":canoe:","category":"travel","emoji_order":"1644","aliases":[":kayak:"],"aliases_ascii":[],"keywords":[]},"speedboat":{"unicode":"1f6a4","unicode_alt":"","code_decimal":"🚤","name":"speedboat","shortname":":speedboat:","category":"travel","emoji_order":"1645","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","boat","vacation","tropical"]},"cruise_ship":{"unicode":"1f6f3","unicode_alt":"1f6f3-fe0f","code_decimal":"🛳","name":"passenger ship","shortname":":cruise_ship:","category":"travel","emoji_order":"1646","aliases":[":passenger_ship:"],"aliases_ascii":[],"keywords":["transportation","travel","boat","vacation"]},"ferry":{"unicode":"26f4","unicode_alt":"26f4-fe0f","code_decimal":"⛴","name":"ferry","shortname":":ferry:","category":"travel","emoji_order":"1647","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","boat","vacation"]},"motorboat":{"unicode":"1f6e5","unicode_alt":"1f6e5-fe0f","code_decimal":"🛥","name":"motorboat","shortname":":motorboat:","category":"travel","emoji_order":"1648","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","boat"]},"ship":{"unicode":"1f6a2","unicode_alt":"","code_decimal":"🚢","name":"ship","shortname":":ship:","category":"travel","emoji_order":"1649","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","boat","vacation"]},"airplane":{"unicode":"2708","unicode_alt":"2708-fe0f","code_decimal":"✈","name":"airplane","shortname":":airplane:","category":"travel","emoji_order":"1650","aliases":[],"aliases_ascii":[],"keywords":["transportation","plane","travel","vacation","fly"]},"airplane_small":{"unicode":"1f6e9","unicode_alt":"1f6e9-fe0f","code_decimal":"🛩","name":"small airplane","shortname":":airplane_small:","category":"travel","emoji_order":"1651","aliases":[":small_airplane:"],"aliases_ascii":[],"keywords":["transportation","plane","travel","vacation","fly"]},"airplane_departure":{"unicode":"1f6eb","unicode_alt":"","code_decimal":"🛫","name":"airplane departure","shortname":":airplane_departure:","category":"travel","emoji_order":"1652","aliases":[],"aliases_ascii":[],"keywords":["transportation","plane","travel","vacation","fly"]},"airplane_arriving":{"unicode":"1f6ec","unicode_alt":"","code_decimal":"🛬","name":"airplane arriving","shortname":":airplane_arriving:","category":"travel","emoji_order":"1653","aliases":[],"aliases_ascii":[],"keywords":["transportation","plane","travel","vacation","fly"]},"seat":{"unicode":"1f4ba","unicode_alt":"","code_decimal":"💺","name":"seat","shortname":":seat:","category":"travel","emoji_order":"1654","aliases":[],"aliases_ascii":[],"keywords":["transportation","object","travel","vacation"]},"helicopter":{"unicode":"1f681","unicode_alt":"","code_decimal":"🚁","name":"helicopter","shortname":":helicopter:","category":"travel","emoji_order":"1655","aliases":[],"aliases_ascii":[],"keywords":["transportation","plane","travel","fly"]},"suspension_railway":{"unicode":"1f69f","unicode_alt":"","code_decimal":"🚟","name":"suspension railway","shortname":":suspension_railway:","category":"travel","emoji_order":"1656","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"mountain_cableway":{"unicode":"1f6a0","unicode_alt":"","code_decimal":"🚠","name":"mountain cableway","shortname":":mountain_cableway:","category":"travel","emoji_order":"1657","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"aerial_tramway":{"unicode":"1f6a1","unicode_alt":"","code_decimal":"🚡","name":"aerial tramway","shortname":":aerial_tramway:","category":"travel","emoji_order":"1658","aliases":[],"aliases_ascii":[],"keywords":["transportation","travel","train"]},"rocket":{"unicode":"1f680","unicode_alt":"","code_decimal":"🚀","name":"rocket","shortname":":rocket:","category":"travel","emoji_order":"1659","aliases":[],"aliases_ascii":[],"keywords":["transportation","object","space","fly","blast"]},"satellite_orbital":{"unicode":"1f6f0","unicode_alt":"1f6f0-fe0f","code_decimal":"🛰","name":"satellite","shortname":":satellite_orbital:","category":"travel","emoji_order":"1660","aliases":[],"aliases_ascii":[],"keywords":["object"]},"bellhop":{"unicode":"1f6ce","unicode_alt":"1f6ce-fe0f","code_decimal":"🛎","name":"bellhop bell","shortname":":bellhop:","category":"objects","emoji_order":"1661","aliases":[":bellhop_bell:"],"aliases_ascii":[],"keywords":["object"]},"door":{"unicode":"1f6aa","unicode_alt":"","code_decimal":"🚪","name":"door","shortname":":door:","category":"objects","emoji_order":"1662","aliases":[],"aliases_ascii":[],"keywords":["object"]},"sleeping_accommodation":{"unicode":"1f6cc","unicode_alt":"","code_decimal":"🛌","name":"sleeping accommodation","shortname":":sleeping_accommodation:","category":"objects","emoji_order":"1663","aliases":[],"aliases_ascii":[],"keywords":["tired"]},"bed":{"unicode":"1f6cf","unicode_alt":"1f6cf-fe0f","code_decimal":"🛏","name":"bed","shortname":":bed:","category":"objects","emoji_order":"1669","aliases":[],"aliases_ascii":[],"keywords":["object","tired"]},"couch":{"unicode":"1f6cb","unicode_alt":"1f6cb-fe0f","code_decimal":"🛋","name":"couch and lamp","shortname":":couch:","category":"objects","emoji_order":"1670","aliases":[":couch_and_lamp:"],"aliases_ascii":[],"keywords":["object"]},"toilet":{"unicode":"1f6bd","unicode_alt":"","code_decimal":"🚽","name":"toilet","shortname":":toilet:","category":"objects","emoji_order":"1671","aliases":[],"aliases_ascii":[],"keywords":["object","bathroom"]},"shower":{"unicode":"1f6bf","unicode_alt":"","code_decimal":"🚿","name":"shower","shortname":":shower:","category":"objects","emoji_order":"1672","aliases":[],"aliases_ascii":[],"keywords":["object","bathroom"]},"bath":{"unicode":"1f6c0","unicode_alt":"","code_decimal":"🛀","name":"bath","shortname":":bath:","category":"activity","emoji_order":"1673","aliases":[],"aliases_ascii":[],"keywords":["bathroom","tired","diversity","steam"]},"bath_tone1":{"unicode":"1f6c0-1f3fb","unicode_alt":"","code_decimal":"🛀🏻","name":"bath tone 1","shortname":":bath_tone1:","category":"activity","emoji_order":"1674","aliases":[],"aliases_ascii":[],"keywords":[]},"bath_tone2":{"unicode":"1f6c0-1f3fc","unicode_alt":"","code_decimal":"🛀🏼","name":"bath tone 2","shortname":":bath_tone2:","category":"activity","emoji_order":"1675","aliases":[],"aliases_ascii":[],"keywords":[]},"bath_tone3":{"unicode":"1f6c0-1f3fd","unicode_alt":"","code_decimal":"🛀🏽","name":"bath tone 3","shortname":":bath_tone3:","category":"activity","emoji_order":"1676","aliases":[],"aliases_ascii":[],"keywords":[]},"bath_tone4":{"unicode":"1f6c0-1f3fe","unicode_alt":"","code_decimal":"🛀🏾","name":"bath tone 4","shortname":":bath_tone4:","category":"activity","emoji_order":"1677","aliases":[],"aliases_ascii":[],"keywords":[]},"bath_tone5":{"unicode":"1f6c0-1f3ff","unicode_alt":"","code_decimal":"🛀🏿","name":"bath tone 5","shortname":":bath_tone5:","category":"activity","emoji_order":"1678","aliases":[],"aliases_ascii":[],"keywords":[]},"bathtub":{"unicode":"1f6c1","unicode_alt":"","code_decimal":"🛁","name":"bathtub","shortname":":bathtub:","category":"objects","emoji_order":"1679","aliases":[],"aliases_ascii":[],"keywords":["object","bathroom","tired","steam"]},"hourglass":{"unicode":"231b","unicode_alt":"231b-fe0f","code_decimal":"⌛","name":"hourglass","shortname":":hourglass:","category":"objects","emoji_order":"1680","aliases":[],"aliases_ascii":[],"keywords":["object","time"]},"hourglass_flowing_sand":{"unicode":"23f3","unicode_alt":"","code_decimal":"⏳","name":"hourglass with flowing sand","shortname":":hourglass_flowing_sand:","category":"objects","emoji_order":"1681","aliases":[],"aliases_ascii":[],"keywords":["object","time"]},"watch":{"unicode":"231a","unicode_alt":"231a-fe0f","code_decimal":"⌚","name":"watch","shortname":":watch:","category":"objects","emoji_order":"1682","aliases":[],"aliases_ascii":[],"keywords":["electronics","time"]},"alarm_clock":{"unicode":"23f0","unicode_alt":"","code_decimal":"⏰","name":"alarm clock","shortname":":alarm_clock:","category":"objects","emoji_order":"1683","aliases":[],"aliases_ascii":[],"keywords":["object","time"]},"stopwatch":{"unicode":"23f1","unicode_alt":"23f1-fe0f","code_decimal":"⏱","name":"stopwatch","shortname":":stopwatch:","category":"objects","emoji_order":"1684","aliases":[],"aliases_ascii":[],"keywords":["electronics","time"]},"timer":{"unicode":"23f2","unicode_alt":"23f2-fe0f","code_decimal":"⏲","name":"timer clock","shortname":":timer:","category":"objects","emoji_order":"1685","aliases":[":timer_clock:"],"aliases_ascii":[],"keywords":["object","time"]},"clock":{"unicode":"1f570","unicode_alt":"1f570-fe0f","code_decimal":"🕰","name":"mantlepiece clock","shortname":":clock:","category":"objects","emoji_order":"1686","aliases":[":mantlepiece_clock:"],"aliases_ascii":[],"keywords":["object","time"]},"clock12":{"unicode":"1f55b","unicode_alt":"","code_decimal":"🕛","name":"clock face twelve oclock","shortname":":clock12:","category":"symbols","emoji_order":"1687","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock1230":{"unicode":"1f567","unicode_alt":"","code_decimal":"🕧","name":"clock face twelve-thirty","shortname":":clock1230:","category":"symbols","emoji_order":"1688","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock1":{"unicode":"1f550","unicode_alt":"","code_decimal":"🕐","name":"clock face one oclock","shortname":":clock1:","category":"symbols","emoji_order":"1689","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock130":{"unicode":"1f55c","unicode_alt":"","code_decimal":"🕜","name":"clock face one-thirty","shortname":":clock130:","category":"symbols","emoji_order":"1690","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock2":{"unicode":"1f551","unicode_alt":"","code_decimal":"🕑","name":"clock face two oclock","shortname":":clock2:","category":"symbols","emoji_order":"1691","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock230":{"unicode":"1f55d","unicode_alt":"","code_decimal":"🕝","name":"clock face two-thirty","shortname":":clock230:","category":"symbols","emoji_order":"1692","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock3":{"unicode":"1f552","unicode_alt":"","code_decimal":"🕒","name":"clock face three oclock","shortname":":clock3:","category":"symbols","emoji_order":"1693","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock330":{"unicode":"1f55e","unicode_alt":"","code_decimal":"🕞","name":"clock face three-thirty","shortname":":clock330:","category":"symbols","emoji_order":"1694","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock4":{"unicode":"1f553","unicode_alt":"","code_decimal":"🕓","name":"clock face four oclock","shortname":":clock4:","category":"symbols","emoji_order":"1695","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock430":{"unicode":"1f55f","unicode_alt":"","code_decimal":"🕟","name":"clock face four-thirty","shortname":":clock430:","category":"symbols","emoji_order":"1696","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock5":{"unicode":"1f554","unicode_alt":"","code_decimal":"🕔","name":"clock face five oclock","shortname":":clock5:","category":"symbols","emoji_order":"1697","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock530":{"unicode":"1f560","unicode_alt":"","code_decimal":"🕠","name":"clock face five-thirty","shortname":":clock530:","category":"symbols","emoji_order":"1698","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock6":{"unicode":"1f555","unicode_alt":"","code_decimal":"🕕","name":"clock face six oclock","shortname":":clock6:","category":"symbols","emoji_order":"1699","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock630":{"unicode":"1f561","unicode_alt":"","code_decimal":"🕡","name":"clock face six-thirty","shortname":":clock630:","category":"symbols","emoji_order":"1700","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock7":{"unicode":"1f556","unicode_alt":"","code_decimal":"🕖","name":"clock face seven oclock","shortname":":clock7:","category":"symbols","emoji_order":"1701","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock730":{"unicode":"1f562","unicode_alt":"","code_decimal":"🕢","name":"clock face seven-thirty","shortname":":clock730:","category":"symbols","emoji_order":"1702","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock8":{"unicode":"1f557","unicode_alt":"","code_decimal":"🕗","name":"clock face eight oclock","shortname":":clock8:","category":"symbols","emoji_order":"1703","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock830":{"unicode":"1f563","unicode_alt":"","code_decimal":"🕣","name":"clock face eight-thirty","shortname":":clock830:","category":"symbols","emoji_order":"1704","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock9":{"unicode":"1f558","unicode_alt":"","code_decimal":"🕘","name":"clock face nine oclock","shortname":":clock9:","category":"symbols","emoji_order":"1705","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock930":{"unicode":"1f564","unicode_alt":"","code_decimal":"🕤","name":"clock face nine-thirty","shortname":":clock930:","category":"symbols","emoji_order":"1706","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock10":{"unicode":"1f559","unicode_alt":"","code_decimal":"🕙","name":"clock face ten oclock","shortname":":clock10:","category":"symbols","emoji_order":"1707","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock1030":{"unicode":"1f565","unicode_alt":"","code_decimal":"🕥","name":"clock face ten-thirty","shortname":":clock1030:","category":"symbols","emoji_order":"1708","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock11":{"unicode":"1f55a","unicode_alt":"","code_decimal":"🕚","name":"clock face eleven oclock","shortname":":clock11:","category":"symbols","emoji_order":"1709","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"clock1130":{"unicode":"1f566","unicode_alt":"","code_decimal":"🕦","name":"clock face eleven-thirty","shortname":":clock1130:","category":"symbols","emoji_order":"1710","aliases":[],"aliases_ascii":[],"keywords":["symbol","time"]},"new_moon":{"unicode":"1f311","unicode_alt":"","code_decimal":"🌑","name":"new moon symbol","shortname":":new_moon:","category":"nature","emoji_order":"1711","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"waxing_crescent_moon":{"unicode":"1f312","unicode_alt":"","code_decimal":"🌒","name":"waxing crescent moon symbol","shortname":":waxing_crescent_moon:","category":"nature","emoji_order":"1712","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"first_quarter_moon":{"unicode":"1f313","unicode_alt":"","code_decimal":"🌓","name":"first quarter moon symbol","shortname":":first_quarter_moon:","category":"nature","emoji_order":"1713","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"waxing_gibbous_moon":{"unicode":"1f314","unicode_alt":"","code_decimal":"🌔","name":"waxing gibbous moon symbol","shortname":":waxing_gibbous_moon:","category":"nature","emoji_order":"1714","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"full_moon":{"unicode":"1f315","unicode_alt":"","code_decimal":"🌕","name":"full moon symbol","shortname":":full_moon:","category":"nature","emoji_order":"1715","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"waning_gibbous_moon":{"unicode":"1f316","unicode_alt":"","code_decimal":"🌖","name":"waning gibbous moon symbol","shortname":":waning_gibbous_moon:","category":"nature","emoji_order":"1716","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"last_quarter_moon":{"unicode":"1f317","unicode_alt":"","code_decimal":"🌗","name":"last quarter moon symbol","shortname":":last_quarter_moon:","category":"nature","emoji_order":"1717","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"waning_crescent_moon":{"unicode":"1f318","unicode_alt":"","code_decimal":"🌘","name":"waning crescent moon symbol","shortname":":waning_crescent_moon:","category":"nature","emoji_order":"1718","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"crescent_moon":{"unicode":"1f319","unicode_alt":"","code_decimal":"🌙","name":"crescent moon","shortname":":crescent_moon:","category":"nature","emoji_order":"1719","aliases":[],"aliases_ascii":[],"keywords":["space","sky","goodnight","moon"]},"new_moon_with_face":{"unicode":"1f31a","unicode_alt":"","code_decimal":"🌚","name":"new moon with face","shortname":":new_moon_with_face:","category":"nature","emoji_order":"1720","aliases":[],"aliases_ascii":[],"keywords":["space","sky","goodnight","moon"]},"first_quarter_moon_with_face":{"unicode":"1f31b","unicode_alt":"","code_decimal":"🌛","name":"first quarter moon with face","shortname":":first_quarter_moon_with_face:","category":"nature","emoji_order":"1721","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"last_quarter_moon_with_face":{"unicode":"1f31c","unicode_alt":"","code_decimal":"🌜","name":"last quarter moon with face","shortname":":last_quarter_moon_with_face:","category":"nature","emoji_order":"1722","aliases":[],"aliases_ascii":[],"keywords":["space","sky","moon"]},"thermometer":{"unicode":"1f321","unicode_alt":"1f321-fe0f","code_decimal":"🌡","name":"thermometer","shortname":":thermometer:","category":"objects","emoji_order":"1723","aliases":[],"aliases_ascii":[],"keywords":["object","science","health","hot"]},"sunny":{"unicode":"2600","unicode_alt":"2600-fe0f","code_decimal":"☀","name":"black sun with rays","shortname":":sunny:","category":"nature","emoji_order":"1724","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","day","sun","hot","morning"]},"full_moon_with_face":{"unicode":"1f31d","unicode_alt":"","code_decimal":"🌝","name":"full moon with face","shortname":":full_moon_with_face:","category":"nature","emoji_order":"1725","aliases":[],"aliases_ascii":[],"keywords":["space","sky","goodnight","moon"]},"sun_with_face":{"unicode":"1f31e","unicode_alt":"","code_decimal":"🌞","name":"sun with face","shortname":":sun_with_face:","category":"nature","emoji_order":"1726","aliases":[],"aliases_ascii":[],"keywords":["sky","day","sun","hump day","morning"]},"star":{"unicode":"2b50","unicode_alt":"2b50-fe0f","code_decimal":"⭐","name":"white medium star","shortname":":star:","category":"nature","emoji_order":"1727","aliases":[],"aliases_ascii":[],"keywords":["space","sky","star"]},"star2":{"unicode":"1f31f","unicode_alt":"","code_decimal":"🌟","name":"glowing star","shortname":":star2:","category":"nature","emoji_order":"1728","aliases":[],"aliases_ascii":[],"keywords":["space","sky","star"]},"stars":{"unicode":"1f320","unicode_alt":"","code_decimal":"🌠","name":"shooting star","shortname":":stars:","category":"travel","emoji_order":"1729","aliases":[],"aliases_ascii":[],"keywords":["space"]},"cloud":{"unicode":"2601","unicode_alt":"2601-fe0f","code_decimal":"☁","name":"cloud","shortname":":cloud:","category":"nature","emoji_order":"1730","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","cloud","cold","rain"]},"partly_sunny":{"unicode":"26c5","unicode_alt":"26c5-fe0f","code_decimal":"⛅","name":"sun behind cloud","shortname":":partly_sunny:","category":"nature","emoji_order":"1731","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","cloud","sun"]},"thunder_cloud_rain":{"unicode":"26c8","unicode_alt":"26c8-fe0f","code_decimal":"⛈","name":"thunder cloud and rain","shortname":":thunder_cloud_rain:","category":"nature","emoji_order":"1732","aliases":[":thunder_cloud_and_rain:"],"aliases_ascii":[],"keywords":["weather","sky","cloud","cold","rain"]},"white_sun_small_cloud":{"unicode":"1f324","unicode_alt":"1f324-fe0f","code_decimal":"🌤","name":"white sun with small cloud","shortname":":white_sun_small_cloud:","category":"nature","emoji_order":"1733","aliases":[":white_sun_with_small_cloud:"],"aliases_ascii":[],"keywords":["weather","sky","cloud","sun"]},"white_sun_cloud":{"unicode":"1f325","unicode_alt":"1f325-fe0f","code_decimal":"🌥","name":"white sun behind cloud","shortname":":white_sun_cloud:","category":"nature","emoji_order":"1734","aliases":[":white_sun_behind_cloud:"],"aliases_ascii":[],"keywords":["weather","sky","cloud","cold","sun"]},"white_sun_rain_cloud":{"unicode":"1f326","unicode_alt":"1f326-fe0f","code_decimal":"🌦","name":"white sun behind cloud with rain","shortname":":white_sun_rain_cloud:","category":"nature","emoji_order":"1735","aliases":[":white_sun_behind_cloud_with_rain:"],"aliases_ascii":[],"keywords":["weather","sky","cloud","cold","rain","sun"]},"cloud_rain":{"unicode":"1f327","unicode_alt":"1f327-fe0f","code_decimal":"🌧","name":"cloud with rain","shortname":":cloud_rain:","category":"nature","emoji_order":"1736","aliases":[":cloud_with_rain:"],"aliases_ascii":[],"keywords":["weather","winter","sky","cloud","cold","rain"]},"cloud_snow":{"unicode":"1f328","unicode_alt":"1f328-fe0f","code_decimal":"🌨","name":"cloud with snow","shortname":":cloud_snow:","category":"nature","emoji_order":"1737","aliases":[":cloud_with_snow:"],"aliases_ascii":[],"keywords":["weather","winter","sky","cloud","cold","snow"]},"cloud_lightning":{"unicode":"1f329","unicode_alt":"1f329-fe0f","code_decimal":"🌩","name":"cloud with lightning","shortname":":cloud_lightning:","category":"nature","emoji_order":"1738","aliases":[":cloud_with_lightning:"],"aliases_ascii":[],"keywords":["weather","sky","cloud","cold","rain"]},"cloud_tornado":{"unicode":"1f32a","unicode_alt":"1f32a-fe0f","code_decimal":"🌪","name":"cloud with tornado","shortname":":cloud_tornado:","category":"nature","emoji_order":"1739","aliases":[":cloud_with_tornado:"],"aliases_ascii":[],"keywords":["weather","sky","cold"]},"fog":{"unicode":"1f32b","unicode_alt":"1f32b-fe0f","code_decimal":"🌫","name":"fog","shortname":":fog:","category":"nature","emoji_order":"1740","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","cold"]},"wind_blowing_face":{"unicode":"1f32c","unicode_alt":"1f32c-fe0f","code_decimal":"🌬","name":"wind blowing face","shortname":":wind_blowing_face:","category":"nature","emoji_order":"1741","aliases":[],"aliases_ascii":[],"keywords":["weather","cold"]},"cyclone":{"unicode":"1f300","unicode_alt":"","code_decimal":"🌀","name":"cyclone","shortname":":cyclone:","category":"symbols","emoji_order":"1742","aliases":[],"aliases_ascii":[],"keywords":["symbol","drugs"]},"rainbow":{"unicode":"1f308","unicode_alt":"","code_decimal":"🌈","name":"rainbow","shortname":":rainbow:","category":"travel","emoji_order":"1743","aliases":[],"aliases_ascii":[],"keywords":["weather","gay","sky","rain"]},"closed_umbrella":{"unicode":"1f302","unicode_alt":"","code_decimal":"🌂","name":"closed umbrella","shortname":":closed_umbrella:","category":"people","emoji_order":"1744","aliases":[],"aliases_ascii":[],"keywords":["object","sky","rain","accessories"]},"umbrella2":{"unicode":"2602","unicode_alt":"2602-fe0f","code_decimal":"☂","name":"umbrella","shortname":":umbrella2:","category":"nature","emoji_order":"1745","aliases":[],"aliases_ascii":[],"keywords":["weather","object","sky","cold"]},"umbrella":{"unicode":"2614","unicode_alt":"2614-fe0f","code_decimal":"☔","name":"umbrella with rain drops","shortname":":umbrella:","category":"nature","emoji_order":"1746","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","cold","rain"]},"beach_umbrella":{"unicode":"26f1","unicode_alt":"26f1-fe0f","code_decimal":"⛱","name":"umbrella on ground","shortname":":beach_umbrella:","category":"objects","emoji_order":"1747","aliases":[":umbrella_on_ground:"],"aliases_ascii":[],"keywords":["travel","vacation","tropical"]},"zap":{"unicode":"26a1","unicode_alt":"26a1-fe0f","code_decimal":"⚡","name":"high voltage sign","shortname":":zap:","category":"nature","emoji_order":"1748","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","diarrhea"]},"snowflake":{"unicode":"2744","unicode_alt":"2744-fe0f","code_decimal":"❄","name":"snowflake","shortname":":snowflake:","category":"nature","emoji_order":"1749","aliases":[],"aliases_ascii":[],"keywords":["weather","winter","sky","holidays","cold","snow"]},"snowman2":{"unicode":"2603","unicode_alt":"2603-fe0f","code_decimal":"☃","name":"snowman","shortname":":snowman2:","category":"nature","emoji_order":"1750","aliases":[],"aliases_ascii":[],"keywords":["weather","winter","holidays","christmas","cold","snow"]},"snowman":{"unicode":"26c4","unicode_alt":"26c4-fe0f","code_decimal":"⛄","name":"snowman without snow","shortname":":snowman:","category":"nature","emoji_order":"1751","aliases":[],"aliases_ascii":[],"keywords":["weather","winter","holidays","cold","snow"]},"comet":{"unicode":"2604","unicode_alt":"2604-fe0f","code_decimal":"☄","name":"comet","shortname":":comet:","category":"nature","emoji_order":"1752","aliases":[],"aliases_ascii":[],"keywords":["space","sky"]},"fire":{"unicode":"1f525","unicode_alt":"","code_decimal":"🔥","name":"fire","shortname":":fire:","category":"nature","emoji_order":"1753","aliases":[":flame:"],"aliases_ascii":[],"keywords":["wth","hot"]},"droplet":{"unicode":"1f4a7","unicode_alt":"","code_decimal":"💧","name":"droplet","shortname":":droplet:","category":"nature","emoji_order":"1754","aliases":[],"aliases_ascii":[],"keywords":["weather","sky","rain"]},"ocean":{"unicode":"1f30a","unicode_alt":"","code_decimal":"🌊","name":"water wave","shortname":":ocean:","category":"nature","emoji_order":"1755","aliases":[],"aliases_ascii":[],"keywords":["weather","boat","tropical","swim"]},"jack_o_lantern":{"unicode":"1f383","unicode_alt":"","code_decimal":"🎃","name":"jack-o-lantern","shortname":":jack_o_lantern:","category":"nature","emoji_order":"1756","aliases":[],"aliases_ascii":[],"keywords":["holidays","halloween"]},"christmas_tree":{"unicode":"1f384","unicode_alt":"","code_decimal":"🎄","name":"christmas tree","shortname":":christmas_tree:","category":"nature","emoji_order":"1757","aliases":[],"aliases_ascii":[],"keywords":["plant","holidays","christmas","trees"]},"fireworks":{"unicode":"1f386","unicode_alt":"","code_decimal":"🎆","name":"fireworks","shortname":":fireworks:","category":"travel","emoji_order":"1758","aliases":[],"aliases_ascii":[],"keywords":["parties"]},"sparkler":{"unicode":"1f387","unicode_alt":"","code_decimal":"🎇","name":"firework sparkler","shortname":":sparkler:","category":"travel","emoji_order":"1759","aliases":[],"aliases_ascii":[],"keywords":["parties"]},"sparkles":{"unicode":"2728","unicode_alt":"","code_decimal":"✨","name":"sparkles","shortname":":sparkles:","category":"nature","emoji_order":"1760","aliases":[],"aliases_ascii":[],"keywords":["star","girls night"]},"balloon":{"unicode":"1f388","unicode_alt":"","code_decimal":"🎈","name":"balloon","shortname":":balloon:","category":"objects","emoji_order":"1761","aliases":[],"aliases_ascii":[],"keywords":["object","birthday","good","parties"]},"tada":{"unicode":"1f389","unicode_alt":"","code_decimal":"🎉","name":"party popper","shortname":":tada:","category":"objects","emoji_order":"1762","aliases":[],"aliases_ascii":[],"keywords":["object","birthday","holidays","cheers","good","girls night","boys night","parties"]},"confetti_ball":{"unicode":"1f38a","unicode_alt":"","code_decimal":"🎊","name":"confetti ball","shortname":":confetti_ball:","category":"objects","emoji_order":"1763","aliases":[],"aliases_ascii":[],"keywords":["object","birthday","holidays","cheers","girls night","boys night","parties"]},"tanabata_tree":{"unicode":"1f38b","unicode_alt":"","code_decimal":"🎋","name":"tanabata tree","shortname":":tanabata_tree:","category":"nature","emoji_order":"1764","aliases":[],"aliases_ascii":[],"keywords":["nature","plant","trees"]},"bamboo":{"unicode":"1f38d","unicode_alt":"","code_decimal":"🎍","name":"pine decoration","shortname":":bamboo:","category":"nature","emoji_order":"1765","aliases":[],"aliases_ascii":[],"keywords":["nature","plant"]},"dolls":{"unicode":"1f38e","unicode_alt":"","code_decimal":"🎎","name":"japanese dolls","shortname":":dolls:","category":"objects","emoji_order":"1766","aliases":[],"aliases_ascii":[],"keywords":["people","japan"]},"flags":{"unicode":"1f38f","unicode_alt":"","code_decimal":"🎏","name":"carp streamer","shortname":":flags:","category":"objects","emoji_order":"1767","aliases":[],"aliases_ascii":[],"keywords":["object","japan"]},"wind_chime":{"unicode":"1f390","unicode_alt":"","code_decimal":"🎐","name":"wind chime","shortname":":wind_chime:","category":"objects","emoji_order":"1768","aliases":[],"aliases_ascii":[],"keywords":["object","japan"]},"rice_scene":{"unicode":"1f391","unicode_alt":"","code_decimal":"🎑","name":"moon viewing ceremony","shortname":":rice_scene:","category":"travel","emoji_order":"1769","aliases":[],"aliases_ascii":[],"keywords":["places","space","sky","travel"]},"ribbon":{"unicode":"1f380","unicode_alt":"","code_decimal":"🎀","name":"ribbon","shortname":":ribbon:","category":"objects","emoji_order":"1770","aliases":[],"aliases_ascii":[],"keywords":["object","gift","birthday"]},"gift":{"unicode":"1f381","unicode_alt":"","code_decimal":"🎁","name":"wrapped present","shortname":":gift:","category":"objects","emoji_order":"1771","aliases":[],"aliases_ascii":[],"keywords":["object","gift","birthday","holidays","christmas","parties"]},"reminder_ribbon":{"unicode":"1f397","unicode_alt":"1f397-fe0f","code_decimal":"🎗","name":"reminder ribbon","shortname":":reminder_ribbon:","category":"activity","emoji_order":"1772","aliases":[],"aliases_ascii":[],"keywords":["award"]},"tickets":{"unicode":"1f39f","unicode_alt":"1f39f-fe0f","code_decimal":"🎟","name":"admission tickets","shortname":":tickets:","category":"activity","emoji_order":"1773","aliases":[":admission_tickets:"],"aliases_ascii":[],"keywords":["theatre","movie","parties"]},"ticket":{"unicode":"1f3ab","unicode_alt":"","code_decimal":"🎫","name":"ticket","shortname":":ticket:","category":"activity","emoji_order":"1774","aliases":[],"aliases_ascii":[],"keywords":["theatre","movie","parties"]},"military_medal":{"unicode":"1f396","unicode_alt":"1f396-fe0f","code_decimal":"🎖","name":"military medal","shortname":":military_medal:","category":"activity","emoji_order":"1775","aliases":[],"aliases_ascii":[],"keywords":["object","award","win"]},"trophy":{"unicode":"1f3c6","unicode_alt":"","code_decimal":"🏆","name":"trophy","shortname":":trophy:","category":"activity","emoji_order":"1776","aliases":[],"aliases_ascii":[],"keywords":["object","game","award","win","perfect","parties"]},"medal":{"unicode":"1f3c5","unicode_alt":"","code_decimal":"🏅","name":"sports medal","shortname":":medal:","category":"activity","emoji_order":"1777","aliases":[":sports_medal:"],"aliases_ascii":[],"keywords":["object","award","sport","win","perfect"]},"first_place":{"unicode":"1f947","unicode_alt":"","code_decimal":"🥇","name":"first place medal","shortname":":first_place:","category":"activity","emoji_order":"1778","aliases":[":first_place_medal:"],"aliases_ascii":[],"keywords":[]},"second_place":{"unicode":"1f948","unicode_alt":"","code_decimal":"🥈","name":"second place medal","shortname":":second_place:","category":"activity","emoji_order":"1779","aliases":[":second_place_medal:"],"aliases_ascii":[],"keywords":[]},"third_place":{"unicode":"1f949","unicode_alt":"","code_decimal":"🥉","name":"third place medal","shortname":":third_place:","category":"activity","emoji_order":"1780","aliases":[":third_place_medal:"],"aliases_ascii":[],"keywords":[]},"soccer":{"unicode":"26bd","unicode_alt":"26bd-fe0f","code_decimal":"⚽","name":"soccer ball","shortname":":soccer:","category":"activity","emoji_order":"1781","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","soccer","football"]},"baseball":{"unicode":"26be","unicode_alt":"26be-fe0f","code_decimal":"⚾","name":"baseball","shortname":":baseball:","category":"activity","emoji_order":"1782","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","baseball"]},"basketball":{"unicode":"1f3c0","unicode_alt":"","code_decimal":"🏀","name":"basketball and hoop","shortname":":basketball:","category":"activity","emoji_order":"1783","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","basketball"]},"volleyball":{"unicode":"1f3d0","unicode_alt":"","code_decimal":"🏐","name":"volleyball","shortname":":volleyball:","category":"activity","emoji_order":"1784","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","volleyball"]},"football":{"unicode":"1f3c8","unicode_alt":"","code_decimal":"🏈","name":"american football","shortname":":football:","category":"activity","emoji_order":"1785","aliases":[],"aliases_ascii":[],"keywords":["america","game","ball","sport","football"]},"rugby_football":{"unicode":"1f3c9","unicode_alt":"","code_decimal":"🏉","name":"rugby football","shortname":":rugby_football:","category":"activity","emoji_order":"1786","aliases":[],"aliases_ascii":[],"keywords":["game","sport","football"]},"tennis":{"unicode":"1f3be","unicode_alt":"","code_decimal":"🎾","name":"tennis racquet and ball","shortname":":tennis:","category":"activity","emoji_order":"1787","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","tennis"]},"8ball":{"unicode":"1f3b1","unicode_alt":"","code_decimal":"🎱","name":"billiards","shortname":":8ball:","category":"activity","emoji_order":"1788","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","billiards","luck","boys night"]},"bowling":{"unicode":"1f3b3","unicode_alt":"","code_decimal":"🎳","name":"bowling","shortname":":bowling:","category":"activity","emoji_order":"1789","aliases":[],"aliases_ascii":[],"keywords":["game","ball","sport","boys night"]},"cricket":{"unicode":"1f3cf","unicode_alt":"","code_decimal":"🏏","name":"cricket bat and ball","shortname":":cricket:","category":"activity","emoji_order":"1790","aliases":[":cricket_bat_ball:"],"aliases_ascii":[],"keywords":["ball","sport","cricket"]},"field_hockey":{"unicode":"1f3d1","unicode_alt":"","code_decimal":"🏑","name":"field hockey stick and ball","shortname":":field_hockey:","category":"activity","emoji_order":"1791","aliases":[],"aliases_ascii":[],"keywords":["ball","sport","hockey"]},"hockey":{"unicode":"1f3d2","unicode_alt":"","code_decimal":"🏒","name":"ice hockey stick and puck","shortname":":hockey:","category":"activity","emoji_order":"1792","aliases":[],"aliases_ascii":[],"keywords":["game","sport","hockey"]},"ping_pong":{"unicode":"1f3d3","unicode_alt":"","code_decimal":"🏓","name":"table tennis paddle and ball","shortname":":ping_pong:","category":"activity","emoji_order":"1793","aliases":[":table_tennis:"],"aliases_ascii":[],"keywords":["game","ball","sport","ping pong"]},"badminton":{"unicode":"1f3f8","unicode_alt":"","code_decimal":"🏸","name":"badminton racquet","shortname":":badminton:","category":"activity","emoji_order":"1794","aliases":[],"aliases_ascii":[],"keywords":["game","sport","badminton"]},"boxing_glove":{"unicode":"1f94a","unicode_alt":"","code_decimal":"🥊","name":"boxing glove","shortname":":boxing_glove:","category":"activity","emoji_order":"1795","aliases":[":boxing_gloves:"],"aliases_ascii":[],"keywords":[]},"martial_arts_uniform":{"unicode":"1f94b","unicode_alt":"","code_decimal":"🥋","name":"martial arts uniform","shortname":":martial_arts_uniform:","category":"activity","emoji_order":"1796","aliases":[":karate_uniform:"],"aliases_ascii":[],"keywords":[]},"goal":{"unicode":"1f945","unicode_alt":"","code_decimal":"🥅","name":"goal net","shortname":":goal:","category":"activity","emoji_order":"1797","aliases":[":goal_net:"],"aliases_ascii":[],"keywords":[]},"dart":{"unicode":"1f3af","unicode_alt":"","code_decimal":"🎯","name":"direct hit","shortname":":dart:","category":"activity","emoji_order":"1798","aliases":[],"aliases_ascii":[],"keywords":["game","sport","boys night"]},"golf":{"unicode":"26f3","unicode_alt":"26f3-fe0f","code_decimal":"⛳","name":"flag in hole","shortname":":golf:","category":"activity","emoji_order":"1799","aliases":[],"aliases_ascii":[],"keywords":["game","ball","vacation","sport","golf"]},"ice_skate":{"unicode":"26f8","unicode_alt":"26f8-fe0f","code_decimal":"⛸","name":"ice skate","shortname":":ice_skate:","category":"activity","emoji_order":"1800","aliases":[],"aliases_ascii":[],"keywords":["cold","sport","ice skating"]},"fishing_pole_and_fish":{"unicode":"1f3a3","unicode_alt":"","code_decimal":"🎣","name":"fishing pole and fish","shortname":":fishing_pole_and_fish:","category":"activity","emoji_order":"1801","aliases":[],"aliases_ascii":[],"keywords":["vacation","sport","fishing"]},"running_shirt_with_sash":{"unicode":"1f3bd","unicode_alt":"","code_decimal":"🎽","name":"running shirt with sash","shortname":":running_shirt_with_sash:","category":"activity","emoji_order":"1802","aliases":[],"aliases_ascii":[],"keywords":["award"]},"ski":{"unicode":"1f3bf","unicode_alt":"","code_decimal":"🎿","name":"ski and ski boot","shortname":":ski:","category":"activity","emoji_order":"1803","aliases":[],"aliases_ascii":[],"keywords":["cold","sport","skiing"]},"video_game":{"unicode":"1f3ae","unicode_alt":"","code_decimal":"🎮","name":"video game","shortname":":video_game:","category":"activity","emoji_order":"1804","aliases":[],"aliases_ascii":[],"keywords":["electronics","game","boys night"]},"joystick":{"unicode":"1f579","unicode_alt":"1f579-fe0f","code_decimal":"🕹","name":"joystick","shortname":":joystick:","category":"objects","emoji_order":"1805","aliases":[],"aliases_ascii":[],"keywords":["electronics","game","boys night"]},"game_die":{"unicode":"1f3b2","unicode_alt":"","code_decimal":"🎲","name":"game die","shortname":":game_die:","category":"activity","emoji_order":"1806","aliases":[],"aliases_ascii":[],"keywords":["object","game","boys night"]},"spades":{"unicode":"2660","unicode_alt":"2660-fe0f","code_decimal":"♠","name":"black spade suit","shortname":":spades:","category":"symbols","emoji_order":"1807","aliases":[],"aliases_ascii":[],"keywords":["symbol","game"]},"hearts":{"unicode":"2665","unicode_alt":"2665-fe0f","code_decimal":"♥","name":"black heart suit","shortname":":hearts:","category":"symbols","emoji_order":"1808","aliases":[],"aliases_ascii":[],"keywords":["love","symbol","game"]},"diamonds":{"unicode":"2666","unicode_alt":"2666-fe0f","code_decimal":"♦","name":"black diamond suit","shortname":":diamonds:","category":"symbols","emoji_order":"1809","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","game"]},"clubs":{"unicode":"2663","unicode_alt":"2663-fe0f","code_decimal":"♣","name":"black club suit","shortname":":clubs:","category":"symbols","emoji_order":"1810","aliases":[],"aliases_ascii":[],"keywords":["symbol","game"]},"black_joker":{"unicode":"1f0cf","unicode_alt":"","code_decimal":"🃏","name":"playing card black joker","shortname":":black_joker:","category":"symbols","emoji_order":"1811","aliases":[],"aliases_ascii":[],"keywords":["object","symbol","game"]},"mahjong":{"unicode":"1f004","unicode_alt":"1f004-fe0f","code_decimal":"🀄","name":"mahjong tile red dragon","shortname":":mahjong:","category":"symbols","emoji_order":"1812","aliases":[],"aliases_ascii":[],"keywords":["object","symbol","game"]},"flower_playing_cards":{"unicode":"1f3b4","unicode_alt":"","code_decimal":"🎴","name":"flower playing cards","shortname":":flower_playing_cards:","category":"symbols","emoji_order":"1813","aliases":[],"aliases_ascii":[],"keywords":["object","symbol"]},"mute":{"unicode":"1f507","unicode_alt":"","code_decimal":"🔇","name":"speaker with cancellation stroke","shortname":":mute:","category":"symbols","emoji_order":"1814","aliases":[],"aliases_ascii":[],"keywords":["alarm","symbol"]},"speaker":{"unicode":"1f508","unicode_alt":"","code_decimal":"🔈","name":"speaker","shortname":":speaker:","category":"symbols","emoji_order":"1815","aliases":[],"aliases_ascii":[],"keywords":["alarm","symbol"]},"sound":{"unicode":"1f509","unicode_alt":"","code_decimal":"🔉","name":"speaker with one sound wave","shortname":":sound:","category":"symbols","emoji_order":"1816","aliases":[],"aliases_ascii":[],"keywords":["alarm","symbol"]},"loud_sound":{"unicode":"1f50a","unicode_alt":"","code_decimal":"🔊","name":"speaker with three sound waves","shortname":":loud_sound:","category":"symbols","emoji_order":"1817","aliases":[],"aliases_ascii":[],"keywords":["alarm","symbol"]},"loudspeaker":{"unicode":"1f4e2","unicode_alt":"","code_decimal":"📢","name":"public address loudspeaker","shortname":":loudspeaker:","category":"symbols","emoji_order":"1818","aliases":[],"aliases_ascii":[],"keywords":["object","alarm","symbol"]},"mega":{"unicode":"1f4e3","unicode_alt":"","code_decimal":"📣","name":"cheering megaphone","shortname":":mega:","category":"symbols","emoji_order":"1819","aliases":[],"aliases_ascii":[],"keywords":["object","sport"]},"postal_horn":{"unicode":"1f4ef","unicode_alt":"","code_decimal":"📯","name":"postal horn","shortname":":postal_horn:","category":"objects","emoji_order":"1820","aliases":[],"aliases_ascii":[],"keywords":["object"]},"bell":{"unicode":"1f514","unicode_alt":"","code_decimal":"🔔","name":"bell","shortname":":bell:","category":"symbols","emoji_order":"1821","aliases":[],"aliases_ascii":[],"keywords":["object","alarm","symbol"]},"no_bell":{"unicode":"1f515","unicode_alt":"","code_decimal":"🔕","name":"bell with cancellation stroke","shortname":":no_bell:","category":"symbols","emoji_order":"1822","aliases":[],"aliases_ascii":[],"keywords":["alarm","symbol"]},"musical_score":{"unicode":"1f3bc","unicode_alt":"","code_decimal":"🎼","name":"musical score","shortname":":musical_score:","category":"activity","emoji_order":"1823","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"musical_note":{"unicode":"1f3b5","unicode_alt":"","code_decimal":"🎵","name":"musical note","shortname":":musical_note:","category":"symbols","emoji_order":"1824","aliases":[],"aliases_ascii":[],"keywords":["instruments","symbol"]},"notes":{"unicode":"1f3b6","unicode_alt":"","code_decimal":"🎶","name":"multiple musical notes","shortname":":notes:","category":"symbols","emoji_order":"1825","aliases":[],"aliases_ascii":[],"keywords":["instruments","symbol"]},"microphone2":{"unicode":"1f399","unicode_alt":"1f399-fe0f","code_decimal":"🎙","name":"studio microphone","shortname":":microphone2:","category":"objects","emoji_order":"1826","aliases":[":studio_microphone:"],"aliases_ascii":[],"keywords":["electronics","object"]},"level_slider":{"unicode":"1f39a","unicode_alt":"1f39a-fe0f","code_decimal":"🎚","name":"level slider","shortname":":level_slider:","category":"objects","emoji_order":"1827","aliases":[],"aliases_ascii":[],"keywords":[]},"control_knobs":{"unicode":"1f39b","unicode_alt":"1f39b-fe0f","code_decimal":"🎛","name":"control knobs","shortname":":control_knobs:","category":"objects","emoji_order":"1828","aliases":[],"aliases_ascii":[],"keywords":["time"]},"microphone":{"unicode":"1f3a4","unicode_alt":"","code_decimal":"🎤","name":"microphone","shortname":":microphone:","category":"activity","emoji_order":"1829","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"headphones":{"unicode":"1f3a7","unicode_alt":"","code_decimal":"🎧","name":"headphone","shortname":":headphones:","category":"activity","emoji_order":"1830","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"radio":{"unicode":"1f4fb","unicode_alt":"","code_decimal":"📻","name":"radio","shortname":":radio:","category":"objects","emoji_order":"1831","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"saxophone":{"unicode":"1f3b7","unicode_alt":"","code_decimal":"🎷","name":"saxophone","shortname":":saxophone:","category":"activity","emoji_order":"1832","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"guitar":{"unicode":"1f3b8","unicode_alt":"","code_decimal":"🎸","name":"guitar","shortname":":guitar:","category":"activity","emoji_order":"1833","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"musical_keyboard":{"unicode":"1f3b9","unicode_alt":"","code_decimal":"🎹","name":"musical keyboard","shortname":":musical_keyboard:","category":"activity","emoji_order":"1834","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"trumpet":{"unicode":"1f3ba","unicode_alt":"","code_decimal":"🎺","name":"trumpet","shortname":":trumpet:","category":"activity","emoji_order":"1835","aliases":[],"aliases_ascii":[],"keywords":["instruments"]},"violin":{"unicode":"1f3bb","unicode_alt":"","code_decimal":"🎻","name":"violin","shortname":":violin:","category":"activity","emoji_order":"1836","aliases":[],"aliases_ascii":[],"keywords":["instruments","sarcastic"]},"drum":{"unicode":"1f941","unicode_alt":"","code_decimal":"🥁","name":"drum with drumsticks","shortname":":drum:","category":"activity","emoji_order":"1837","aliases":[":drum_with_drumsticks:"],"aliases_ascii":[],"keywords":[]},"iphone":{"unicode":"1f4f1","unicode_alt":"","code_decimal":"📱","name":"mobile phone","shortname":":iphone:","category":"objects","emoji_order":"1838","aliases":[],"aliases_ascii":[],"keywords":["electronics","phone","selfie"]},"calling":{"unicode":"1f4f2","unicode_alt":"","code_decimal":"📲","name":"mobile phone with rightwards arrow at left","shortname":":calling:","category":"objects","emoji_order":"1839","aliases":[],"aliases_ascii":[],"keywords":["electronics","phone","selfie"]},"telephone":{"unicode":"260e","unicode_alt":"260e-fe0f","code_decimal":"☎","name":"black telephone","shortname":":telephone:","category":"objects","emoji_order":"1840","aliases":[],"aliases_ascii":[],"keywords":["electronics","phone"]},"telephone_receiver":{"unicode":"1f4de","unicode_alt":"","code_decimal":"📞","name":"telephone receiver","shortname":":telephone_receiver:","category":"objects","emoji_order":"1841","aliases":[],"aliases_ascii":[],"keywords":["electronics","phone"]},"pager":{"unicode":"1f4df","unicode_alt":"","code_decimal":"📟","name":"pager","shortname":":pager:","category":"objects","emoji_order":"1842","aliases":[],"aliases_ascii":[],"keywords":["electronics","work"]},"fax":{"unicode":"1f4e0","unicode_alt":"","code_decimal":"📠","name":"fax machine","shortname":":fax:","category":"objects","emoji_order":"1843","aliases":[],"aliases_ascii":[],"keywords":["electronics","work","office"]},"battery":{"unicode":"1f50b","unicode_alt":"","code_decimal":"🔋","name":"battery","shortname":":battery:","category":"objects","emoji_order":"1844","aliases":[],"aliases_ascii":[],"keywords":["object"]},"electric_plug":{"unicode":"1f50c","unicode_alt":"","code_decimal":"🔌","name":"electric plug","shortname":":electric_plug:","category":"objects","emoji_order":"1845","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"computer":{"unicode":"1f4bb","unicode_alt":"","code_decimal":"💻","name":"personal computer","shortname":":computer:","category":"objects","emoji_order":"1846","aliases":[],"aliases_ascii":[],"keywords":["electronics","work","office"]},"desktop":{"unicode":"1f5a5","unicode_alt":"1f5a5-fe0f","code_decimal":"🖥","name":"desktop computer","shortname":":desktop:","category":"objects","emoji_order":"1847","aliases":[":desktop_computer:"],"aliases_ascii":[],"keywords":["electronics","work"]},"printer":{"unicode":"1f5a8","unicode_alt":"1f5a8-fe0f","code_decimal":"🖨","name":"printer","shortname":":printer:","category":"objects","emoji_order":"1848","aliases":[],"aliases_ascii":[],"keywords":["electronics","work","office"]},"keyboard":{"unicode":"2328","unicode_alt":"2328-fe0f","code_decimal":"⌨","name":"keyboard","shortname":":keyboard:","category":"objects","emoji_order":"1849","aliases":[],"aliases_ascii":[],"keywords":["electronics","work","office"]},"mouse_three_button":{"unicode":"1f5b1","unicode_alt":"1f5b1-fe0f","code_decimal":"🖱","name":"three button mouse","shortname":":mouse_three_button:","category":"objects","emoji_order":"1850","aliases":[":three_button_mouse:"],"aliases_ascii":[],"keywords":["electronics","work","game","office"]},"trackball":{"unicode":"1f5b2","unicode_alt":"1f5b2-fe0f","code_decimal":"🖲","name":"trackball","shortname":":trackball:","category":"objects","emoji_order":"1851","aliases":[],"aliases_ascii":[],"keywords":["electronics","work","game","office"]},"minidisc":{"unicode":"1f4bd","unicode_alt":"","code_decimal":"💽","name":"minidisc","shortname":":minidisc:","category":"objects","emoji_order":"1852","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"floppy_disk":{"unicode":"1f4be","unicode_alt":"","code_decimal":"💾","name":"floppy disk","shortname":":floppy_disk:","category":"objects","emoji_order":"1853","aliases":[],"aliases_ascii":[],"keywords":["electronics","office"]},"cd":{"unicode":"1f4bf","unicode_alt":"","code_decimal":"💿","name":"optical disc","shortname":":cd:","category":"objects","emoji_order":"1854","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"dvd":{"unicode":"1f4c0","unicode_alt":"","code_decimal":"📀","name":"dvd","shortname":":dvd:","category":"objects","emoji_order":"1855","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"movie_camera":{"unicode":"1f3a5","unicode_alt":"","code_decimal":"🎥","name":"movie camera","shortname":":movie_camera:","category":"objects","emoji_order":"1856","aliases":[],"aliases_ascii":[],"keywords":["object","camera","movie"]},"film_frames":{"unicode":"1f39e","unicode_alt":"1f39e-fe0f","code_decimal":"🎞","name":"film frames","shortname":":film_frames:","category":"objects","emoji_order":"1857","aliases":[],"aliases_ascii":[],"keywords":["object","camera","movie"]},"projector":{"unicode":"1f4fd","unicode_alt":"1f4fd-fe0f","code_decimal":"📽","name":"film projector","shortname":":projector:","category":"objects","emoji_order":"1858","aliases":[":film_projector:"],"aliases_ascii":[],"keywords":["object","camera","movie"]},"clapper":{"unicode":"1f3ac","unicode_alt":"","code_decimal":"🎬","name":"clapper board","shortname":":clapper:","category":"activity","emoji_order":"1859","aliases":[],"aliases_ascii":[],"keywords":["movie"]},"tv":{"unicode":"1f4fa","unicode_alt":"","code_decimal":"📺","name":"television","shortname":":tv:","category":"objects","emoji_order":"1860","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"camera":{"unicode":"1f4f7","unicode_alt":"","code_decimal":"📷","name":"camera","shortname":":camera:","category":"objects","emoji_order":"1861","aliases":[],"aliases_ascii":[],"keywords":["electronics","camera","selfie"]},"camera_with_flash":{"unicode":"1f4f8","unicode_alt":"","code_decimal":"📸","name":"camera with flash","shortname":":camera_with_flash:","category":"objects","emoji_order":"1862","aliases":[],"aliases_ascii":[],"keywords":["electronics","camera"]},"video_camera":{"unicode":"1f4f9","unicode_alt":"","code_decimal":"📹","name":"video camera","shortname":":video_camera:","category":"objects","emoji_order":"1863","aliases":[],"aliases_ascii":[],"keywords":["electronics","camera","movie"]},"vhs":{"unicode":"1f4fc","unicode_alt":"","code_decimal":"📼","name":"videocassette","shortname":":vhs:","category":"objects","emoji_order":"1864","aliases":[],"aliases_ascii":[],"keywords":["electronics"]},"mag":{"unicode":"1f50d","unicode_alt":"","code_decimal":"🔍","name":"left-pointing magnifying glass","shortname":":mag:","category":"objects","emoji_order":"1865","aliases":[],"aliases_ascii":[],"keywords":["object"]},"mag_right":{"unicode":"1f50e","unicode_alt":"","code_decimal":"🔎","name":"right-pointing magnifying glass","shortname":":mag_right:","category":"objects","emoji_order":"1866","aliases":[],"aliases_ascii":[],"keywords":["object"]},"microscope":{"unicode":"1f52c","unicode_alt":"","code_decimal":"🔬","name":"microscope","shortname":":microscope:","category":"objects","emoji_order":"1867","aliases":[],"aliases_ascii":[],"keywords":["object","science"]},"telescope":{"unicode":"1f52d","unicode_alt":"","code_decimal":"🔭","name":"telescope","shortname":":telescope:","category":"objects","emoji_order":"1868","aliases":[],"aliases_ascii":[],"keywords":["object","space","science"]},"satellite":{"unicode":"1f4e1","unicode_alt":"","code_decimal":"📡","name":"satellite antenna","shortname":":satellite:","category":"objects","emoji_order":"1869","aliases":[],"aliases_ascii":[],"keywords":["object"]},"candle":{"unicode":"1f56f","unicode_alt":"1f56f-fe0f","code_decimal":"🕯","name":"candle","shortname":":candle:","category":"objects","emoji_order":"1870","aliases":[],"aliases_ascii":[],"keywords":["object"]},"bulb":{"unicode":"1f4a1","unicode_alt":"","code_decimal":"💡","name":"electric light bulb","shortname":":bulb:","category":"objects","emoji_order":"1871","aliases":[],"aliases_ascii":[],"keywords":["object","science"]},"flashlight":{"unicode":"1f526","unicode_alt":"","code_decimal":"🔦","name":"electric torch","shortname":":flashlight:","category":"objects","emoji_order":"1872","aliases":[],"aliases_ascii":[],"keywords":["electronics","object"]},"izakaya_lantern":{"unicode":"1f3ee","unicode_alt":"","code_decimal":"🏮","name":"izakaya lantern","shortname":":izakaya_lantern:","category":"objects","emoji_order":"1873","aliases":[],"aliases_ascii":[],"keywords":["object","japan"]},"notebook_with_decorative_cover":{"unicode":"1f4d4","unicode_alt":"","code_decimal":"📔","name":"notebook with decorative cover","shortname":":notebook_with_decorative_cover:","category":"objects","emoji_order":"1874","aliases":[],"aliases_ascii":[],"keywords":["object","office","write"]},"closed_book":{"unicode":"1f4d5","unicode_alt":"","code_decimal":"📕","name":"closed book","shortname":":closed_book:","category":"objects","emoji_order":"1875","aliases":[],"aliases_ascii":[],"keywords":["object","office","write","book"]},"book":{"unicode":"1f4d6","unicode_alt":"","code_decimal":"📖","name":"open book","shortname":":book:","category":"objects","emoji_order":"1876","aliases":[],"aliases_ascii":[],"keywords":["object","office","write","book"]},"green_book":{"unicode":"1f4d7","unicode_alt":"","code_decimal":"📗","name":"green book","shortname":":green_book:","category":"objects","emoji_order":"1877","aliases":[],"aliases_ascii":[],"keywords":["object","office","book"]},"blue_book":{"unicode":"1f4d8","unicode_alt":"","code_decimal":"📘","name":"blue book","shortname":":blue_book:","category":"objects","emoji_order":"1878","aliases":[],"aliases_ascii":[],"keywords":["object","office","write","book"]},"orange_book":{"unicode":"1f4d9","unicode_alt":"","code_decimal":"📙","name":"orange book","shortname":":orange_book:","category":"objects","emoji_order":"1879","aliases":[],"aliases_ascii":[],"keywords":["object","office","write","book"]},"books":{"unicode":"1f4da","unicode_alt":"","code_decimal":"📚","name":"books","shortname":":books:","category":"objects","emoji_order":"1880","aliases":[],"aliases_ascii":[],"keywords":["object","office","write","book"]},"notebook":{"unicode":"1f4d3","unicode_alt":"","code_decimal":"📓","name":"notebook","shortname":":notebook:","category":"objects","emoji_order":"1881","aliases":[],"aliases_ascii":[],"keywords":["object","office","write"]},"ledger":{"unicode":"1f4d2","unicode_alt":"","code_decimal":"📒","name":"ledger","shortname":":ledger:","category":"objects","emoji_order":"1882","aliases":[],"aliases_ascii":[],"keywords":["object","office","write"]},"page_with_curl":{"unicode":"1f4c3","unicode_alt":"","code_decimal":"📃","name":"page with curl","shortname":":page_with_curl:","category":"objects","emoji_order":"1883","aliases":[],"aliases_ascii":[],"keywords":["office","write"]},"scroll":{"unicode":"1f4dc","unicode_alt":"","code_decimal":"📜","name":"scroll","shortname":":scroll:","category":"objects","emoji_order":"1884","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"page_facing_up":{"unicode":"1f4c4","unicode_alt":"","code_decimal":"📄","name":"page facing up","shortname":":page_facing_up:","category":"objects","emoji_order":"1885","aliases":[],"aliases_ascii":[],"keywords":["work","office","write"]},"newspaper":{"unicode":"1f4f0","unicode_alt":"","code_decimal":"📰","name":"newspaper","shortname":":newspaper:","category":"objects","emoji_order":"1886","aliases":[],"aliases_ascii":[],"keywords":["office","write"]},"newspaper2":{"unicode":"1f5de","unicode_alt":"1f5de-fe0f","code_decimal":"🗞","name":"rolled-up newspaper","shortname":":newspaper2:","category":"objects","emoji_order":"1887","aliases":[":rolled_up_newspaper:"],"aliases_ascii":[],"keywords":["office","write"]},"bookmark_tabs":{"unicode":"1f4d1","unicode_alt":"","code_decimal":"📑","name":"bookmark tabs","shortname":":bookmark_tabs:","category":"objects","emoji_order":"1888","aliases":[],"aliases_ascii":[],"keywords":["office","write"]},"bookmark":{"unicode":"1f516","unicode_alt":"","code_decimal":"🔖","name":"bookmark","shortname":":bookmark:","category":"objects","emoji_order":"1889","aliases":[],"aliases_ascii":[],"keywords":["object","book"]},"label":{"unicode":"1f3f7","unicode_alt":"1f3f7-fe0f","code_decimal":"🏷","name":"label","shortname":":label:","category":"objects","emoji_order":"1890","aliases":[],"aliases_ascii":[],"keywords":["object"]},"moneybag":{"unicode":"1f4b0","unicode_alt":"","code_decimal":"💰","name":"money bag","shortname":":moneybag:","category":"objects","emoji_order":"1891","aliases":[],"aliases_ascii":[],"keywords":["bag","award","money"]},"yen":{"unicode":"1f4b4","unicode_alt":"","code_decimal":"💴","name":"banknote with yen sign","shortname":":yen:","category":"objects","emoji_order":"1892","aliases":[],"aliases_ascii":[],"keywords":["money"]},"dollar":{"unicode":"1f4b5","unicode_alt":"","code_decimal":"💵","name":"banknote with dollar sign","shortname":":dollar:","category":"objects","emoji_order":"1893","aliases":[],"aliases_ascii":[],"keywords":["money"]},"euro":{"unicode":"1f4b6","unicode_alt":"","code_decimal":"💶","name":"banknote with euro sign","shortname":":euro:","category":"objects","emoji_order":"1894","aliases":[],"aliases_ascii":[],"keywords":["money"]},"pound":{"unicode":"1f4b7","unicode_alt":"","code_decimal":"💷","name":"banknote with pound sign","shortname":":pound:","category":"objects","emoji_order":"1895","aliases":[],"aliases_ascii":[],"keywords":["money"]},"money_with_wings":{"unicode":"1f4b8","unicode_alt":"","code_decimal":"💸","name":"money with wings","shortname":":money_with_wings:","category":"objects","emoji_order":"1896","aliases":[],"aliases_ascii":[],"keywords":["money","boys night"]},"credit_card":{"unicode":"1f4b3","unicode_alt":"","code_decimal":"💳","name":"credit card","shortname":":credit_card:","category":"objects","emoji_order":"1897","aliases":[],"aliases_ascii":[],"keywords":["object","money","boys night"]},"chart":{"unicode":"1f4b9","unicode_alt":"","code_decimal":"💹","name":"chart with upwards trend and yen sign","shortname":":chart:","category":"symbols","emoji_order":"1898","aliases":[],"aliases_ascii":[],"keywords":["symbol","money"]},"currency_exchange":{"unicode":"1f4b1","unicode_alt":"","code_decimal":"💱","name":"currency exchange","shortname":":currency_exchange:","category":"symbols","emoji_order":"1899","aliases":[],"aliases_ascii":[],"keywords":["symbol","money"]},"heavy_dollar_sign":{"unicode":"1f4b2","unicode_alt":"","code_decimal":"💲","name":"heavy dollar sign","shortname":":heavy_dollar_sign:","category":"symbols","emoji_order":"1900","aliases":[],"aliases_ascii":[],"keywords":["math","symbol","money"]},"envelope":{"unicode":"2709","unicode_alt":"2709-fe0f","code_decimal":"✉","name":"envelope","shortname":":envelope:","category":"objects","emoji_order":"1901","aliases":[],"aliases_ascii":[],"keywords":["object","office","write"]},"e-mail":{"unicode":"1f4e7","unicode_alt":"","code_decimal":"📧","name":"e-mail symbol","shortname":":e-mail:","category":"objects","emoji_order":"1902","aliases":[":email:"],"aliases_ascii":[],"keywords":["office"]},"incoming_envelope":{"unicode":"1f4e8","unicode_alt":"","code_decimal":"📨","name":"incoming envelope","shortname":":incoming_envelope:","category":"objects","emoji_order":"1903","aliases":[],"aliases_ascii":[],"keywords":["object"]},"envelope_with_arrow":{"unicode":"1f4e9","unicode_alt":"","code_decimal":"📩","name":"envelope with downwards arrow above","shortname":":envelope_with_arrow:","category":"objects","emoji_order":"1904","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"outbox_tray":{"unicode":"1f4e4","unicode_alt":"","code_decimal":"📤","name":"outbox tray","shortname":":outbox_tray:","category":"objects","emoji_order":"1905","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"inbox_tray":{"unicode":"1f4e5","unicode_alt":"","code_decimal":"📥","name":"inbox tray","shortname":":inbox_tray:","category":"objects","emoji_order":"1906","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"package":{"unicode":"1f4e6","unicode_alt":"","code_decimal":"📦","name":"package","shortname":":package:","category":"objects","emoji_order":"1907","aliases":[],"aliases_ascii":[],"keywords":["object","gift","office"]},"mailbox":{"unicode":"1f4eb","unicode_alt":"","code_decimal":"📫","name":"closed mailbox with raised flag","shortname":":mailbox:","category":"objects","emoji_order":"1908","aliases":[],"aliases_ascii":[],"keywords":["object"]},"mailbox_closed":{"unicode":"1f4ea","unicode_alt":"","code_decimal":"📪","name":"closed mailbox with lowered flag","shortname":":mailbox_closed:","category":"objects","emoji_order":"1909","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"mailbox_with_mail":{"unicode":"1f4ec","unicode_alt":"","code_decimal":"📬","name":"open mailbox with raised flag","shortname":":mailbox_with_mail:","category":"objects","emoji_order":"1910","aliases":[],"aliases_ascii":[],"keywords":["object"]},"mailbox_with_no_mail":{"unicode":"1f4ed","unicode_alt":"","code_decimal":"📭","name":"open mailbox with lowered flag","shortname":":mailbox_with_no_mail:","category":"objects","emoji_order":"1911","aliases":[],"aliases_ascii":[],"keywords":["object"]},"postbox":{"unicode":"1f4ee","unicode_alt":"","code_decimal":"📮","name":"postbox","shortname":":postbox:","category":"objects","emoji_order":"1912","aliases":[],"aliases_ascii":[],"keywords":["object"]},"ballot_box":{"unicode":"1f5f3","unicode_alt":"1f5f3-fe0f","code_decimal":"🗳","name":"ballot box with ballot","shortname":":ballot_box:","category":"objects","emoji_order":"1913","aliases":[":ballot_box_with_ballot:"],"aliases_ascii":[],"keywords":["object","office"]},"pencil2":{"unicode":"270f","unicode_alt":"270f-fe0f","code_decimal":"✏","name":"pencil","shortname":":pencil2:","category":"objects","emoji_order":"1914","aliases":[],"aliases_ascii":[],"keywords":["object","office","write"]},"black_nib":{"unicode":"2712","unicode_alt":"2712-fe0f","code_decimal":"✒","name":"black nib","shortname":":black_nib:","category":"objects","emoji_order":"1915","aliases":[],"aliases_ascii":[],"keywords":["object","office","write"]},"pen_fountain":{"unicode":"1f58b","unicode_alt":"1f58b-fe0f","code_decimal":"🖋","name":"lower left fountain pen","shortname":":pen_fountain:","category":"objects","emoji_order":"1916","aliases":[":lower_left_fountain_pen:"],"aliases_ascii":[],"keywords":["object","office","write"]},"pen_ballpoint":{"unicode":"1f58a","unicode_alt":"1f58a-fe0f","code_decimal":"🖊","name":"lower left ballpoint pen","shortname":":pen_ballpoint:","category":"objects","emoji_order":"1917","aliases":[":lower_left_ballpoint_pen:"],"aliases_ascii":[],"keywords":["object","office","write"]},"paintbrush":{"unicode":"1f58c","unicode_alt":"1f58c-fe0f","code_decimal":"🖌","name":"lower left paintbrush","shortname":":paintbrush:","category":"objects","emoji_order":"1918","aliases":[":lower_left_paintbrush:"],"aliases_ascii":[],"keywords":["object","office","write"]},"crayon":{"unicode":"1f58d","unicode_alt":"1f58d-fe0f","code_decimal":"🖍","name":"lower left crayon","shortname":":crayon:","category":"objects","emoji_order":"1919","aliases":[":lower_left_crayon:"],"aliases_ascii":[],"keywords":["object","office","write"]},"pencil":{"unicode":"1f4dd","unicode_alt":"","code_decimal":"📝","name":"memo","shortname":":pencil:","category":"objects","emoji_order":"1920","aliases":[],"aliases_ascii":[],"keywords":["work","office","write"]},"briefcase":{"unicode":"1f4bc","unicode_alt":"","code_decimal":"💼","name":"briefcase","shortname":":briefcase:","category":"people","emoji_order":"1921","aliases":[],"aliases_ascii":[],"keywords":["bag","work","accessories","nutcase","job"]},"file_folder":{"unicode":"1f4c1","unicode_alt":"","code_decimal":"📁","name":"file folder","shortname":":file_folder:","category":"objects","emoji_order":"1922","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"open_file_folder":{"unicode":"1f4c2","unicode_alt":"","code_decimal":"📂","name":"open file folder","shortname":":open_file_folder:","category":"objects","emoji_order":"1923","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"dividers":{"unicode":"1f5c2","unicode_alt":"1f5c2-fe0f","code_decimal":"🗂","name":"card index dividers","shortname":":dividers:","category":"objects","emoji_order":"1924","aliases":[":card_index_dividers:"],"aliases_ascii":[],"keywords":["work","office"]},"date":{"unicode":"1f4c5","unicode_alt":"","code_decimal":"📅","name":"calendar","shortname":":date:","category":"objects","emoji_order":"1925","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"calendar":{"unicode":"1f4c6","unicode_alt":"","code_decimal":"📆","name":"tear-off calendar","shortname":":calendar:","category":"objects","emoji_order":"1926","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"notepad_spiral":{"unicode":"1f5d2","unicode_alt":"1f5d2-fe0f","code_decimal":"🗒","name":"spiral note pad","shortname":":notepad_spiral:","category":"objects","emoji_order":"1927","aliases":[":spiral_note_pad:"],"aliases_ascii":[],"keywords":["work","office","write"]},"calendar_spiral":{"unicode":"1f5d3","unicode_alt":"1f5d3-fe0f","code_decimal":"🗓","name":"spiral calendar pad","shortname":":calendar_spiral:","category":"objects","emoji_order":"1928","aliases":[":spiral_calendar_pad:"],"aliases_ascii":[],"keywords":["object","office"]},"card_index":{"unicode":"1f4c7","unicode_alt":"","code_decimal":"📇","name":"card index","shortname":":card_index:","category":"objects","emoji_order":"1929","aliases":[],"aliases_ascii":[],"keywords":["object","work","office"]},"chart_with_upwards_trend":{"unicode":"1f4c8","unicode_alt":"","code_decimal":"📈","name":"chart with upwards trend","shortname":":chart_with_upwards_trend:","category":"objects","emoji_order":"1930","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"chart_with_downwards_trend":{"unicode":"1f4c9","unicode_alt":"","code_decimal":"📉","name":"chart with downwards trend","shortname":":chart_with_downwards_trend:","category":"objects","emoji_order":"1931","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"bar_chart":{"unicode":"1f4ca","unicode_alt":"","code_decimal":"📊","name":"bar chart","shortname":":bar_chart:","category":"objects","emoji_order":"1932","aliases":[],"aliases_ascii":[],"keywords":["work","office"]},"clipboard":{"unicode":"1f4cb","unicode_alt":"","code_decimal":"📋","name":"clipboard","shortname":":clipboard:","category":"objects","emoji_order":"1933","aliases":[],"aliases_ascii":[],"keywords":["object","work","office","write"]},"pushpin":{"unicode":"1f4cc","unicode_alt":"","code_decimal":"📌","name":"pushpin","shortname":":pushpin:","category":"objects","emoji_order":"1934","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"round_pushpin":{"unicode":"1f4cd","unicode_alt":"","code_decimal":"📍","name":"round pushpin","shortname":":round_pushpin:","category":"objects","emoji_order":"1935","aliases":[],"aliases_ascii":[],"keywords":["object","office"]},"paperclip":{"unicode":"1f4ce","unicode_alt":"","code_decimal":"📎","name":"paperclip","shortname":":paperclip:","category":"objects","emoji_order":"1936","aliases":[],"aliases_ascii":[],"keywords":["object","work","office"]},"paperclips":{"unicode":"1f587","unicode_alt":"1f587-fe0f","code_decimal":"🖇","name":"linked paperclips","shortname":":paperclips:","category":"objects","emoji_order":"1937","aliases":[":linked_paperclips:"],"aliases_ascii":[],"keywords":["object","work","office"]},"straight_ruler":{"unicode":"1f4cf","unicode_alt":"","code_decimal":"📏","name":"straight ruler","shortname":":straight_ruler:","category":"objects","emoji_order":"1938","aliases":[],"aliases_ascii":[],"keywords":["object","tool","office"]},"triangular_ruler":{"unicode":"1f4d0","unicode_alt":"","code_decimal":"📐","name":"triangular ruler","shortname":":triangular_ruler:","category":"objects","emoji_order":"1939","aliases":[],"aliases_ascii":[],"keywords":["object","tool","office"]},"scissors":{"unicode":"2702","unicode_alt":"2702-fe0f","code_decimal":"✂","name":"black scissors","shortname":":scissors:","category":"objects","emoji_order":"1940","aliases":[],"aliases_ascii":[],"keywords":["object","tool","weapon","office"]},"card_box":{"unicode":"1f5c3","unicode_alt":"1f5c3-fe0f","code_decimal":"🗃","name":"card file box","shortname":":card_box:","category":"objects","emoji_order":"1941","aliases":[":card_file_box:"],"aliases_ascii":[],"keywords":["object","work","office"]},"file_cabinet":{"unicode":"1f5c4","unicode_alt":"1f5c4-fe0f","code_decimal":"🗄","name":"file cabinet","shortname":":file_cabinet:","category":"objects","emoji_order":"1942","aliases":[],"aliases_ascii":[],"keywords":["object","work","office"]},"wastebasket":{"unicode":"1f5d1","unicode_alt":"1f5d1-fe0f","code_decimal":"🗑","name":"wastebasket","shortname":":wastebasket:","category":"objects","emoji_order":"1943","aliases":[],"aliases_ascii":[],"keywords":["object","work"]},"lock":{"unicode":"1f512","unicode_alt":"","code_decimal":"🔒","name":"lock","shortname":":lock:","category":"objects","emoji_order":"1944","aliases":[],"aliases_ascii":[],"keywords":["object","lock"]},"unlock":{"unicode":"1f513","unicode_alt":"","code_decimal":"🔓","name":"open lock","shortname":":unlock:","category":"objects","emoji_order":"1945","aliases":[],"aliases_ascii":[],"keywords":["object","lock"]},"lock_with_ink_pen":{"unicode":"1f50f","unicode_alt":"","code_decimal":"🔏","name":"lock with ink pen","shortname":":lock_with_ink_pen:","category":"objects","emoji_order":"1946","aliases":[],"aliases_ascii":[],"keywords":["object","lock"]},"closed_lock_with_key":{"unicode":"1f510","unicode_alt":"","code_decimal":"🔐","name":"closed lock with key","shortname":":closed_lock_with_key:","category":"objects","emoji_order":"1947","aliases":[],"aliases_ascii":[],"keywords":["object","lock"]},"key":{"unicode":"1f511","unicode_alt":"","code_decimal":"🔑","name":"key","shortname":":key:","category":"objects","emoji_order":"1948","aliases":[],"aliases_ascii":[],"keywords":["object","lock"]},"key2":{"unicode":"1f5dd","unicode_alt":"1f5dd-fe0f","code_decimal":"🗝","name":"old key","shortname":":key2:","category":"objects","emoji_order":"1949","aliases":[":old_key:"],"aliases_ascii":[],"keywords":["object","lock"]},"hammer":{"unicode":"1f528","unicode_alt":"","code_decimal":"🔨","name":"hammer","shortname":":hammer:","category":"objects","emoji_order":"1950","aliases":[],"aliases_ascii":[],"keywords":["object","tool","weapon"]},"pick":{"unicode":"26cf","unicode_alt":"26cf-fe0f","code_decimal":"⛏","name":"pick","shortname":":pick:","category":"objects","emoji_order":"1951","aliases":[],"aliases_ascii":[],"keywords":["object","tool","weapon"]},"hammer_pick":{"unicode":"2692","unicode_alt":"2692-fe0f","code_decimal":"⚒","name":"hammer and pick","shortname":":hammer_pick:","category":"objects","emoji_order":"1952","aliases":[":hammer_and_pick:"],"aliases_ascii":[],"keywords":["object","tool","weapon"]},"tools":{"unicode":"1f6e0","unicode_alt":"1f6e0-fe0f","code_decimal":"🛠","name":"hammer and wrench","shortname":":tools:","category":"objects","emoji_order":"1953","aliases":[":hammer_and_wrench:"],"aliases_ascii":[],"keywords":["object","tool"]},"dagger":{"unicode":"1f5e1","unicode_alt":"1f5e1-fe0f","code_decimal":"🗡","name":"dagger knife","shortname":":dagger:","category":"objects","emoji_order":"1954","aliases":[":dagger_knife:"],"aliases_ascii":[],"keywords":["object","weapon"]},"crossed_swords":{"unicode":"2694","unicode_alt":"2694-fe0f","code_decimal":"⚔","name":"crossed swords","shortname":":crossed_swords:","category":"objects","emoji_order":"1955","aliases":[],"aliases_ascii":[],"keywords":["object","weapon"]},"gun":{"unicode":"1f52b","unicode_alt":"","code_decimal":"🔫","name":"pistol","shortname":":gun:","category":"objects","emoji_order":"1956","aliases":[],"aliases_ascii":[],"keywords":["object","weapon","dead","gun","sarcastic"]},"bow_and_arrow":{"unicode":"1f3f9","unicode_alt":"","code_decimal":"🏹","name":"bow and arrow","shortname":":bow_and_arrow:","category":"activity","emoji_order":"1957","aliases":[":archery:"],"aliases_ascii":[],"keywords":["weapon","sport"]},"shield":{"unicode":"1f6e1","unicode_alt":"1f6e1-fe0f","code_decimal":"🛡","name":"shield","shortname":":shield:","category":"objects","emoji_order":"1958","aliases":[],"aliases_ascii":[],"keywords":["object"]},"wrench":{"unicode":"1f527","unicode_alt":"","code_decimal":"🔧","name":"wrench","shortname":":wrench:","category":"objects","emoji_order":"1959","aliases":[],"aliases_ascii":[],"keywords":["object","tool"]},"nut_and_bolt":{"unicode":"1f529","unicode_alt":"","code_decimal":"🔩","name":"nut and bolt","shortname":":nut_and_bolt:","category":"objects","emoji_order":"1960","aliases":[],"aliases_ascii":[],"keywords":["object","tool","nutcase"]},"gear":{"unicode":"2699","unicode_alt":"2699-fe0f","code_decimal":"⚙","name":"gear","shortname":":gear:","category":"objects","emoji_order":"1961","aliases":[],"aliases_ascii":[],"keywords":["object","tool"]},"compression":{"unicode":"1f5dc","unicode_alt":"1f5dc-fe0f","code_decimal":"🗜","name":"compression","shortname":":compression:","category":"objects","emoji_order":"1962","aliases":[],"aliases_ascii":[],"keywords":[]},"alembic":{"unicode":"2697","unicode_alt":"2697-fe0f","code_decimal":"⚗","name":"alembic","shortname":":alembic:","category":"objects","emoji_order":"1963","aliases":[],"aliases_ascii":[],"keywords":["object","science"]},"scales":{"unicode":"2696","unicode_alt":"2696-fe0f","code_decimal":"⚖","name":"scales","shortname":":scales:","category":"objects","emoji_order":"1964","aliases":[],"aliases_ascii":[],"keywords":["object"]},"link":{"unicode":"1f517","unicode_alt":"","code_decimal":"🔗","name":"link symbol","shortname":":link:","category":"objects","emoji_order":"1965","aliases":[],"aliases_ascii":[],"keywords":["symbol","office"]},"chains":{"unicode":"26d3","unicode_alt":"26d3-fe0f","code_decimal":"⛓","name":"chains","shortname":":chains:","category":"objects","emoji_order":"1966","aliases":[],"aliases_ascii":[],"keywords":["object","tool"]},"syringe":{"unicode":"1f489","unicode_alt":"","code_decimal":"💉","name":"syringe","shortname":":syringe:","category":"objects","emoji_order":"1967","aliases":[],"aliases_ascii":[],"keywords":["object","weapon","health","drugs"]},"pill":{"unicode":"1f48a","unicode_alt":"","code_decimal":"💊","name":"pill","shortname":":pill:","category":"objects","emoji_order":"1968","aliases":[],"aliases_ascii":[],"keywords":["object","health","drugs"]},"smoking":{"unicode":"1f6ac","unicode_alt":"","code_decimal":"🚬","name":"smoking symbol","shortname":":smoking:","category":"objects","emoji_order":"1969","aliases":[],"aliases_ascii":[],"keywords":["symbol","drugs","smoking"]},"coffin":{"unicode":"26b0","unicode_alt":"26b0-fe0f","code_decimal":"⚰","name":"coffin","shortname":":coffin:","category":"objects","emoji_order":"1970","aliases":[],"aliases_ascii":[],"keywords":["object","dead","rip"]},"urn":{"unicode":"26b1","unicode_alt":"26b1-fe0f","code_decimal":"⚱","name":"funeral urn","shortname":":urn:","category":"objects","emoji_order":"1971","aliases":[":funeral_urn:"],"aliases_ascii":[],"keywords":["object","dead","rip"]},"moyai":{"unicode":"1f5ff","unicode_alt":"","code_decimal":"🗿","name":"moyai","shortname":":moyai:","category":"objects","emoji_order":"1972","aliases":[],"aliases_ascii":[],"keywords":["travel","vacation"]},"oil":{"unicode":"1f6e2","unicode_alt":"1f6e2-fe0f","code_decimal":"🛢","name":"oil drum","shortname":":oil:","category":"objects","emoji_order":"1973","aliases":[":oil_drum:"],"aliases_ascii":[],"keywords":["object"]},"crystal_ball":{"unicode":"1f52e","unicode_alt":"","code_decimal":"🔮","name":"crystal ball","shortname":":crystal_ball:","category":"objects","emoji_order":"1974","aliases":[],"aliases_ascii":[],"keywords":["object","ball"]},"shopping_cart":{"unicode":"1f6d2","unicode_alt":"","code_decimal":"🛒","name":"shopping trolley","shortname":":shopping_cart:","category":"objects","emoji_order":"1975","aliases":[":shopping_trolley:"],"aliases_ascii":[],"keywords":[]},"atm":{"unicode":"1f3e7","unicode_alt":"","code_decimal":"🏧","name":"automated teller machine","shortname":":atm:","category":"symbols","emoji_order":"1976","aliases":[],"aliases_ascii":[],"keywords":["electronics","symbol","money"]},"put_litter_in_its_place":{"unicode":"1f6ae","unicode_alt":"","code_decimal":"🚮","name":"put litter in its place symbol","shortname":":put_litter_in_its_place:","category":"symbols","emoji_order":"1977","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"potable_water":{"unicode":"1f6b0","unicode_alt":"","code_decimal":"🚰","name":"potable water symbol","shortname":":potable_water:","category":"symbols","emoji_order":"1978","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"wheelchair":{"unicode":"267f","unicode_alt":"267f-fe0f","code_decimal":"♿","name":"wheelchair symbol","shortname":":wheelchair:","category":"symbols","emoji_order":"1979","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"mens":{"unicode":"1f6b9","unicode_alt":"","code_decimal":"🚹","name":"mens symbol","shortname":":mens:","category":"symbols","emoji_order":"1980","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"womens":{"unicode":"1f6ba","unicode_alt":"","code_decimal":"🚺","name":"womens symbol","shortname":":womens:","category":"symbols","emoji_order":"1981","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"restroom":{"unicode":"1f6bb","unicode_alt":"","code_decimal":"🚻","name":"restroom","shortname":":restroom:","category":"symbols","emoji_order":"1982","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"baby_symbol":{"unicode":"1f6bc","unicode_alt":"","code_decimal":"🚼","name":"baby symbol","shortname":":baby_symbol:","category":"symbols","emoji_order":"1983","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"wc":{"unicode":"1f6be","unicode_alt":"","code_decimal":"🚾","name":"water closet","shortname":":wc:","category":"symbols","emoji_order":"1984","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"passport_control":{"unicode":"1f6c2","unicode_alt":"","code_decimal":"🛂","name":"passport control","shortname":":passport_control:","category":"symbols","emoji_order":"1985","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"customs":{"unicode":"1f6c3","unicode_alt":"","code_decimal":"🛃","name":"customs","shortname":":customs:","category":"symbols","emoji_order":"1986","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"baggage_claim":{"unicode":"1f6c4","unicode_alt":"","code_decimal":"🛄","name":"baggage claim","shortname":":baggage_claim:","category":"symbols","emoji_order":"1987","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"left_luggage":{"unicode":"1f6c5","unicode_alt":"","code_decimal":"🛅","name":"left luggage","shortname":":left_luggage:","category":"symbols","emoji_order":"1988","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"warning":{"unicode":"26a0","unicode_alt":"26a0-fe0f","code_decimal":"⚠","name":"warning sign","shortname":":warning:","category":"symbols","emoji_order":"1989","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation"]},"children_crossing":{"unicode":"1f6b8","unicode_alt":"","code_decimal":"🚸","name":"children crossing","shortname":":children_crossing:","category":"symbols","emoji_order":"1990","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"no_entry":{"unicode":"26d4","unicode_alt":"26d4-fe0f","code_decimal":"⛔","name":"no entry","shortname":":no_entry:","category":"symbols","emoji_order":"1991","aliases":[],"aliases_ascii":[],"keywords":["symbol","circle"]},"no_entry_sign":{"unicode":"1f6ab","unicode_alt":"","code_decimal":"🚫","name":"no entry sign","shortname":":no_entry_sign:","category":"symbols","emoji_order":"1992","aliases":[],"aliases_ascii":[],"keywords":["symbol","circle"]},"no_bicycles":{"unicode":"1f6b3","unicode_alt":"","code_decimal":"🚳","name":"no bicycles","shortname":":no_bicycles:","category":"symbols","emoji_order":"1993","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"no_smoking":{"unicode":"1f6ad","unicode_alt":"","code_decimal":"🚭","name":"no smoking symbol","shortname":":no_smoking:","category":"symbols","emoji_order":"1994","aliases":[],"aliases_ascii":[],"keywords":["symbol","smoking"]},"do_not_litter":{"unicode":"1f6af","unicode_alt":"","code_decimal":"🚯","name":"do not litter symbol","shortname":":do_not_litter:","category":"symbols","emoji_order":"1995","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"non-potable_water":{"unicode":"1f6b1","unicode_alt":"","code_decimal":"🚱","name":"non-potable water symbol","shortname":":non-potable_water:","category":"symbols","emoji_order":"1996","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"no_pedestrians":{"unicode":"1f6b7","unicode_alt":"","code_decimal":"🚷","name":"no pedestrians","shortname":":no_pedestrians:","category":"symbols","emoji_order":"1997","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"no_mobile_phones":{"unicode":"1f4f5","unicode_alt":"","code_decimal":"📵","name":"no mobile phones","shortname":":no_mobile_phones:","category":"symbols","emoji_order":"1998","aliases":[],"aliases_ascii":[],"keywords":["symbol","phone"]},"underage":{"unicode":"1f51e","unicode_alt":"","code_decimal":"🔞","name":"no one under eighteen symbol","shortname":":underage:","category":"symbols","emoji_order":"1999","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"radioactive":{"unicode":"2622","unicode_alt":"2622-fe0f","code_decimal":"☢","name":"radioactive sign","shortname":":radioactive:","category":"symbols","emoji_order":"2000","aliases":[":radioactive_sign:"],"aliases_ascii":[],"keywords":["symbol","science"]},"biohazard":{"unicode":"2623","unicode_alt":"2623-fe0f","code_decimal":"☣","name":"biohazard sign","shortname":":biohazard:","category":"symbols","emoji_order":"2001","aliases":[":biohazard_sign:"],"aliases_ascii":[],"keywords":["symbol","science"]},"arrow_up":{"unicode":"2b06","unicode_alt":"2b06-fe0f","code_decimal":"⬆","name":"upwards black arrow","shortname":":arrow_up:","category":"symbols","emoji_order":"2002","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_upper_right":{"unicode":"2197","unicode_alt":"2197-fe0f","code_decimal":"↗","name":"north east arrow","shortname":":arrow_upper_right:","category":"symbols","emoji_order":"2003","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_right":{"unicode":"27a1","unicode_alt":"27a1-fe0f","code_decimal":"➡","name":"black rightwards arrow","shortname":":arrow_right:","category":"symbols","emoji_order":"2004","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_lower_right":{"unicode":"2198","unicode_alt":"2198-fe0f","code_decimal":"↘","name":"south east arrow","shortname":":arrow_lower_right:","category":"symbols","emoji_order":"2005","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_down":{"unicode":"2b07","unicode_alt":"2b07-fe0f","code_decimal":"⬇","name":"downwards black arrow","shortname":":arrow_down:","category":"symbols","emoji_order":"2006","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_lower_left":{"unicode":"2199","unicode_alt":"2199-fe0f","code_decimal":"↙","name":"south west arrow","shortname":":arrow_lower_left:","category":"symbols","emoji_order":"2007","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_left":{"unicode":"2b05","unicode_alt":"2b05-fe0f","code_decimal":"⬅","name":"leftwards black arrow","shortname":":arrow_left:","category":"symbols","emoji_order":"2008","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_upper_left":{"unicode":"2196","unicode_alt":"2196-fe0f","code_decimal":"↖","name":"north west arrow","shortname":":arrow_upper_left:","category":"symbols","emoji_order":"2009","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_up_down":{"unicode":"2195","unicode_alt":"2195-fe0f","code_decimal":"↕","name":"up down arrow","shortname":":arrow_up_down:","category":"symbols","emoji_order":"2010","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"left_right_arrow":{"unicode":"2194","unicode_alt":"2194-fe0f","code_decimal":"↔","name":"left right arrow","shortname":":left_right_arrow:","category":"symbols","emoji_order":"2011","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"leftwards_arrow_with_hook":{"unicode":"21a9","unicode_alt":"21a9-fe0f","code_decimal":"↩","name":"leftwards arrow with hook","shortname":":leftwards_arrow_with_hook:","category":"symbols","emoji_order":"2012","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_right_hook":{"unicode":"21aa","unicode_alt":"21aa-fe0f","code_decimal":"↪","name":"rightwards arrow with hook","shortname":":arrow_right_hook:","category":"symbols","emoji_order":"2013","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_heading_up":{"unicode":"2934","unicode_alt":"2934-fe0f","code_decimal":"⤴","name":"arrow pointing rightwards then curving upwards","shortname":":arrow_heading_up:","category":"symbols","emoji_order":"2014","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_heading_down":{"unicode":"2935","unicode_alt":"2935-fe0f","code_decimal":"⤵","name":"arrow pointing rightwards then curving downwards","shortname":":arrow_heading_down:","category":"symbols","emoji_order":"2015","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrows_clockwise":{"unicode":"1f503","unicode_alt":"","code_decimal":"🔃","name":"clockwise downwards and upwards open circle arrows","shortname":":arrows_clockwise:","category":"symbols","emoji_order":"2016","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrows_counterclockwise":{"unicode":"1f504","unicode_alt":"","code_decimal":"🔄","name":"anticlockwise downwards and upwards open circle arrows","shortname":":arrows_counterclockwise:","category":"symbols","emoji_order":"2017","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"back":{"unicode":"1f519","unicode_alt":"","code_decimal":"🔙","name":"back with leftwards arrow above","shortname":":back:","category":"symbols","emoji_order":"2018","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"end":{"unicode":"1f51a","unicode_alt":"","code_decimal":"🔚","name":"end with leftwards arrow above","shortname":":end:","category":"symbols","emoji_order":"2019","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"on":{"unicode":"1f51b","unicode_alt":"","code_decimal":"🔛","name":"on with exclamation mark with left right arrow abo","shortname":":on:","category":"symbols","emoji_order":"2020","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"soon":{"unicode":"1f51c","unicode_alt":"","code_decimal":"🔜","name":"soon with rightwards arrow above","shortname":":soon:","category":"symbols","emoji_order":"2021","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"top":{"unicode":"1f51d","unicode_alt":"","code_decimal":"🔝","name":"top with upwards arrow above","shortname":":top:","category":"symbols","emoji_order":"2022","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"place_of_worship":{"unicode":"1f6d0","unicode_alt":"","code_decimal":"🛐","name":"place of worship","shortname":":place_of_worship:","category":"symbols","emoji_order":"2023","aliases":[":worship_symbol:"],"aliases_ascii":[],"keywords":["religion","symbol","pray"]},"atom":{"unicode":"269b","unicode_alt":"269b-fe0f","code_decimal":"⚛","name":"atom symbol","shortname":":atom:","category":"symbols","emoji_order":"2024","aliases":[":atom_symbol:"],"aliases_ascii":[],"keywords":["symbol","science"]},"om_symbol":{"unicode":"1f549","unicode_alt":"1f549-fe0f","code_decimal":"🕉","name":"om symbol","shortname":":om_symbol:","category":"symbols","emoji_order":"2025","aliases":[],"aliases_ascii":[],"keywords":["religion","symbol"]},"star_of_david":{"unicode":"2721","unicode_alt":"2721-fe0f","code_decimal":"✡","name":"star of david","shortname":":star_of_david:","category":"symbols","emoji_order":"2026","aliases":[],"aliases_ascii":[],"keywords":["religion","jew","star","symbol"]},"wheel_of_dharma":{"unicode":"2638","unicode_alt":"2638-fe0f","code_decimal":"☸","name":"wheel of dharma","shortname":":wheel_of_dharma:","category":"symbols","emoji_order":"2027","aliases":[],"aliases_ascii":[],"keywords":["religion","symbol"]},"yin_yang":{"unicode":"262f","unicode_alt":"262f-fe0f","code_decimal":"☯","name":"yin yang","shortname":":yin_yang:","category":"symbols","emoji_order":"2028","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"cross":{"unicode":"271d","unicode_alt":"271d-fe0f","code_decimal":"✝","name":"latin cross","shortname":":cross:","category":"symbols","emoji_order":"2029","aliases":[":latin_cross:"],"aliases_ascii":[],"keywords":["religion","symbol"]},"orthodox_cross":{"unicode":"2626","unicode_alt":"2626-fe0f","code_decimal":"☦","name":"orthodox cross","shortname":":orthodox_cross:","category":"symbols","emoji_order":"2030","aliases":[],"aliases_ascii":[],"keywords":["religion","symbol"]},"star_and_crescent":{"unicode":"262a","unicode_alt":"262a-fe0f","code_decimal":"☪","name":"star and crescent","shortname":":star_and_crescent:","category":"symbols","emoji_order":"2031","aliases":[],"aliases_ascii":[],"keywords":["religion","symbol"]},"peace":{"unicode":"262e","unicode_alt":"262e-fe0f","code_decimal":"☮","name":"peace symbol","shortname":":peace:","category":"symbols","emoji_order":"2032","aliases":[":peace_symbol:"],"aliases_ascii":[],"keywords":["symbol","peace","drugs"]},"menorah":{"unicode":"1f54e","unicode_alt":"","code_decimal":"🕎","name":"menorah with nine branches","shortname":":menorah:","category":"symbols","emoji_order":"2033","aliases":[],"aliases_ascii":[],"keywords":["religion","object","jew","symbol","holidays"]},"six_pointed_star":{"unicode":"1f52f","unicode_alt":"","code_decimal":"🔯","name":"six pointed star with middle dot","shortname":":six_pointed_star:","category":"symbols","emoji_order":"2034","aliases":[],"aliases_ascii":[],"keywords":["religion","jew","star","symbol"]},"aries":{"unicode":"2648","unicode_alt":"2648-fe0f","code_decimal":"♈","name":"aries","shortname":":aries:","category":"symbols","emoji_order":"2035","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"taurus":{"unicode":"2649","unicode_alt":"2649-fe0f","code_decimal":"♉","name":"taurus","shortname":":taurus:","category":"symbols","emoji_order":"2036","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"gemini":{"unicode":"264a","unicode_alt":"264a-fe0f","code_decimal":"♊","name":"gemini","shortname":":gemini:","category":"symbols","emoji_order":"2037","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"cancer":{"unicode":"264b","unicode_alt":"264b-fe0f","code_decimal":"♋","name":"cancer","shortname":":cancer:","category":"symbols","emoji_order":"2038","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"leo":{"unicode":"264c","unicode_alt":"264c-fe0f","code_decimal":"♌","name":"leo","shortname":":leo:","category":"symbols","emoji_order":"2039","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"virgo":{"unicode":"264d","unicode_alt":"264d-fe0f","code_decimal":"♍","name":"virgo","shortname":":virgo:","category":"symbols","emoji_order":"2040","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"libra":{"unicode":"264e","unicode_alt":"264e-fe0f","code_decimal":"♎","name":"libra","shortname":":libra:","category":"symbols","emoji_order":"2041","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"scorpius":{"unicode":"264f","unicode_alt":"264f-fe0f","code_decimal":"♏","name":"scorpius","shortname":":scorpius:","category":"symbols","emoji_order":"2042","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"sagittarius":{"unicode":"2650","unicode_alt":"2650-fe0f","code_decimal":"♐","name":"sagittarius","shortname":":sagittarius:","category":"symbols","emoji_order":"2043","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"capricorn":{"unicode":"2651","unicode_alt":"2651-fe0f","code_decimal":"♑","name":"capricorn","shortname":":capricorn:","category":"symbols","emoji_order":"2044","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"aquarius":{"unicode":"2652","unicode_alt":"2652-fe0f","code_decimal":"♒","name":"aquarius","shortname":":aquarius:","category":"symbols","emoji_order":"2045","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"pisces":{"unicode":"2653","unicode_alt":"2653-fe0f","code_decimal":"♓","name":"pisces","shortname":":pisces:","category":"symbols","emoji_order":"2046","aliases":[],"aliases_ascii":[],"keywords":["zodiac","symbol"]},"ophiuchus":{"unicode":"26ce","unicode_alt":"","code_decimal":"⛎","name":"ophiuchus","shortname":":ophiuchus:","category":"symbols","emoji_order":"2047","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"twisted_rightwards_arrows":{"unicode":"1f500","unicode_alt":"","code_decimal":"🔀","name":"twisted rightwards arrows","shortname":":twisted_rightwards_arrows:","category":"symbols","emoji_order":"2048","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"repeat":{"unicode":"1f501","unicode_alt":"","code_decimal":"🔁","name":"clockwise rightwards and leftwards open circle arrows","shortname":":repeat:","category":"symbols","emoji_order":"2049","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"repeat_one":{"unicode":"1f502","unicode_alt":"","code_decimal":"🔂","name":"clockwise rightwards and leftwards open circle arrows with circled one overlay","shortname":":repeat_one:","category":"symbols","emoji_order":"2050","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_forward":{"unicode":"25b6","unicode_alt":"25b6-fe0f","code_decimal":"▶","name":"black right-pointing triangle","shortname":":arrow_forward:","category":"symbols","emoji_order":"2051","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol","triangle"]},"fast_forward":{"unicode":"23e9","unicode_alt":"","code_decimal":"⏩","name":"black right-pointing double triangle","shortname":":fast_forward:","category":"symbols","emoji_order":"2052","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"track_next":{"unicode":"23ed","unicode_alt":"23ed-fe0f","code_decimal":"⏭","name":"black right-pointing double triangle with vertical bar","shortname":":track_next:","category":"symbols","emoji_order":"2053","aliases":[":next_track:"],"aliases_ascii":[],"keywords":["arrow","symbol"]},"play_pause":{"unicode":"23ef","unicode_alt":"23ef-fe0f","code_decimal":"⏯","name":"black right-pointing double triangle with double vertical bar","shortname":":play_pause:","category":"symbols","emoji_order":"2054","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_backward":{"unicode":"25c0","unicode_alt":"25c0-fe0f","code_decimal":"◀","name":"black left-pointing triangle","shortname":":arrow_backward:","category":"symbols","emoji_order":"2055","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol","triangle"]},"rewind":{"unicode":"23ea","unicode_alt":"","code_decimal":"⏪","name":"black left-pointing double triangle","shortname":":rewind:","category":"symbols","emoji_order":"2056","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"track_previous":{"unicode":"23ee","unicode_alt":"23ee-fe0f","code_decimal":"⏮","name":"black left-pointing double triangle with vertical bar","shortname":":track_previous:","category":"symbols","emoji_order":"2057","aliases":[":previous_track:"],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_up_small":{"unicode":"1f53c","unicode_alt":"","code_decimal":"🔼","name":"up-pointing small red triangle","shortname":":arrow_up_small:","category":"symbols","emoji_order":"2058","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol","triangle"]},"arrow_double_up":{"unicode":"23eb","unicode_alt":"","code_decimal":"⏫","name":"black up-pointing double triangle","shortname":":arrow_double_up:","category":"symbols","emoji_order":"2059","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"arrow_down_small":{"unicode":"1f53d","unicode_alt":"","code_decimal":"🔽","name":"down-pointing small red triangle","shortname":":arrow_down_small:","category":"symbols","emoji_order":"2060","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol","triangle"]},"arrow_double_down":{"unicode":"23ec","unicode_alt":"","code_decimal":"⏬","name":"black down-pointing double triangle","shortname":":arrow_double_down:","category":"symbols","emoji_order":"2061","aliases":[],"aliases_ascii":[],"keywords":["arrow","symbol"]},"pause_button":{"unicode":"23f8","unicode_alt":"23f8-fe0f","code_decimal":"⏸","name":"double vertical bar","shortname":":pause_button:","category":"symbols","emoji_order":"2062","aliases":[":double_vertical_bar:"],"aliases_ascii":[],"keywords":["symbol"]},"stop_button":{"unicode":"23f9","unicode_alt":"23f9-fe0f","code_decimal":"⏹","name":"black square for stop","shortname":":stop_button:","category":"symbols","emoji_order":"2063","aliases":[],"aliases_ascii":[],"keywords":["symbol","square"]},"record_button":{"unicode":"23fa","unicode_alt":"23fa-fe0f","code_decimal":"⏺","name":"black circle for record","shortname":":record_button:","category":"symbols","emoji_order":"2064","aliases":[],"aliases_ascii":[],"keywords":["symbol","circle"]},"eject":{"unicode":"23cf","unicode_alt":"23cf-fe0f","code_decimal":"⏏","name":"eject symbol","shortname":":eject:","category":"symbols","emoji_order":"2065","aliases":[":eject_symbol:"],"aliases_ascii":[],"keywords":[]},"cinema":{"unicode":"1f3a6","unicode_alt":"","code_decimal":"🎦","name":"cinema","shortname":":cinema:","category":"symbols","emoji_order":"2066","aliases":[],"aliases_ascii":[],"keywords":["symbol","camera","movie"]},"low_brightness":{"unicode":"1f505","unicode_alt":"","code_decimal":"🔅","name":"low brightness symbol","shortname":":low_brightness:","category":"symbols","emoji_order":"2067","aliases":[],"aliases_ascii":[],"keywords":["symbol","sun"]},"high_brightness":{"unicode":"1f506","unicode_alt":"","code_decimal":"🔆","name":"high brightness symbol","shortname":":high_brightness:","category":"symbols","emoji_order":"2068","aliases":[],"aliases_ascii":[],"keywords":["symbol","sun"]},"signal_strength":{"unicode":"1f4f6","unicode_alt":"","code_decimal":"📶","name":"antenna with bars","shortname":":signal_strength:","category":"symbols","emoji_order":"2069","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"vibration_mode":{"unicode":"1f4f3","unicode_alt":"","code_decimal":"📳","name":"vibration mode","shortname":":vibration_mode:","category":"symbols","emoji_order":"2070","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"mobile_phone_off":{"unicode":"1f4f4","unicode_alt":"","code_decimal":"📴","name":"mobile phone off","shortname":":mobile_phone_off:","category":"symbols","emoji_order":"2071","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"recycle":{"unicode":"267b","unicode_alt":"267b-fe0f","code_decimal":"♻","name":"black universal recycling symbol","shortname":":recycle:","category":"symbols","emoji_order":"2072","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"name_badge":{"unicode":"1f4db","unicode_alt":"","code_decimal":"📛","name":"name badge","shortname":":name_badge:","category":"symbols","emoji_order":"2073","aliases":[],"aliases_ascii":[],"keywords":["work"]},"fleur-de-lis":{"unicode":"269c","unicode_alt":"269c-fe0f","code_decimal":"⚜","name":"fleur-de-lis","shortname":":fleur-de-lis:","category":"symbols","emoji_order":"2074","aliases":[],"aliases_ascii":[],"keywords":["object","symbol"]},"beginner":{"unicode":"1f530","unicode_alt":"","code_decimal":"🔰","name":"japanese symbol for beginner","shortname":":beginner:","category":"symbols","emoji_order":"2075","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"trident":{"unicode":"1f531","unicode_alt":"","code_decimal":"🔱","name":"trident emblem","shortname":":trident:","category":"symbols","emoji_order":"2076","aliases":[],"aliases_ascii":[],"keywords":["object","symbol"]},"o":{"unicode":"2b55","unicode_alt":"2b55-fe0f","code_decimal":"⭕","name":"heavy large circle","shortname":":o:","category":"symbols","emoji_order":"2077","aliases":[],"aliases_ascii":[],"keywords":["symbol","circle"]},"white_check_mark":{"unicode":"2705","unicode_alt":"","code_decimal":"✅","name":"white heavy check mark","shortname":":white_check_mark:","category":"symbols","emoji_order":"2078","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"ballot_box_with_check":{"unicode":"2611","unicode_alt":"2611-fe0f","code_decimal":"☑","name":"ballot box with check","shortname":":ballot_box_with_check:","category":"symbols","emoji_order":"2079","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"heavy_check_mark":{"unicode":"2714","unicode_alt":"2714-fe0f","code_decimal":"✔","name":"heavy check mark","shortname":":heavy_check_mark:","category":"symbols","emoji_order":"2080","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"heavy_multiplication_x":{"unicode":"2716","unicode_alt":"2716-fe0f","code_decimal":"✖","name":"heavy multiplication x","shortname":":heavy_multiplication_x:","category":"symbols","emoji_order":"2081","aliases":[],"aliases_ascii":[],"keywords":["math","symbol"]},"x":{"unicode":"274c","unicode_alt":"","code_decimal":"❌","name":"cross mark","shortname":":x:","category":"symbols","emoji_order":"2082","aliases":[],"aliases_ascii":[],"keywords":["symbol","sol"]},"negative_squared_cross_mark":{"unicode":"274e","unicode_alt":"","code_decimal":"❎","name":"negative squared cross mark","shortname":":negative_squared_cross_mark:","category":"symbols","emoji_order":"2083","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"heavy_plus_sign":{"unicode":"2795","unicode_alt":"","code_decimal":"➕","name":"heavy plus sign","shortname":":heavy_plus_sign:","category":"symbols","emoji_order":"2084","aliases":[],"aliases_ascii":[],"keywords":["math","symbol"]},"heavy_minus_sign":{"unicode":"2796","unicode_alt":"","code_decimal":"➖","name":"heavy minus sign","shortname":":heavy_minus_sign:","category":"symbols","emoji_order":"2088","aliases":[],"aliases_ascii":[],"keywords":["math","symbol"]},"heavy_division_sign":{"unicode":"2797","unicode_alt":"","code_decimal":"➗","name":"heavy division sign","shortname":":heavy_division_sign:","category":"symbols","emoji_order":"2089","aliases":[],"aliases_ascii":[],"keywords":["math","symbol"]},"curly_loop":{"unicode":"27b0","unicode_alt":"","code_decimal":"➰","name":"curly loop","shortname":":curly_loop:","category":"symbols","emoji_order":"2090","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"loop":{"unicode":"27bf","unicode_alt":"","code_decimal":"➿","name":"double curly loop","shortname":":loop:","category":"symbols","emoji_order":"2091","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"part_alternation_mark":{"unicode":"303d","unicode_alt":"303d-fe0f","code_decimal":"〽","name":"part alternation mark","shortname":":part_alternation_mark:","category":"symbols","emoji_order":"2092","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"eight_spoked_asterisk":{"unicode":"2733","unicode_alt":"2733-fe0f","code_decimal":"✳","name":"eight spoked asterisk","shortname":":eight_spoked_asterisk:","category":"symbols","emoji_order":"2093","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"eight_pointed_black_star":{"unicode":"2734","unicode_alt":"2734-fe0f","code_decimal":"✴","name":"eight pointed black star","shortname":":eight_pointed_black_star:","category":"symbols","emoji_order":"2094","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"sparkle":{"unicode":"2747","unicode_alt":"2747-fe0f","code_decimal":"❇","name":"sparkle","shortname":":sparkle:","category":"symbols","emoji_order":"2095","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"bangbang":{"unicode":"203c","unicode_alt":"203c-fe0f","code_decimal":"‼","name":"double exclamation mark","shortname":":bangbang:","category":"symbols","emoji_order":"2096","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation"]},"interrobang":{"unicode":"2049","unicode_alt":"2049-fe0f","code_decimal":"⁉","name":"exclamation question mark","shortname":":interrobang:","category":"symbols","emoji_order":"2097","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation"]},"question":{"unicode":"2753","unicode_alt":"","code_decimal":"❓","name":"black question mark ornament","shortname":":question:","category":"symbols","emoji_order":"2098","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation","wth"]},"grey_question":{"unicode":"2754","unicode_alt":"","code_decimal":"❔","name":"white question mark ornament","shortname":":grey_question:","category":"symbols","emoji_order":"2099","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation"]},"grey_exclamation":{"unicode":"2755","unicode_alt":"","code_decimal":"❕","name":"white exclamation mark ornament","shortname":":grey_exclamation:","category":"symbols","emoji_order":"2100","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation"]},"exclamation":{"unicode":"2757","unicode_alt":"2757-fe0f","code_decimal":"❗","name":"heavy exclamation mark symbol","shortname":":exclamation:","category":"symbols","emoji_order":"2101","aliases":[],"aliases_ascii":[],"keywords":["symbol","punctuation"]},"wavy_dash":{"unicode":"3030","unicode_alt":"3030-fe0f","code_decimal":"〰","name":"wavy dash","shortname":":wavy_dash:","category":"symbols","emoji_order":"2102","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"copyright":{"unicode":"00a9","unicode_alt":"00a9-fe0f","code_decimal":"©","name":"copyright sign","shortname":":copyright:","category":"symbols","emoji_order":"2103","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"registered":{"unicode":"00ae","unicode_alt":"00ae-fe0f","code_decimal":"®","name":"registered sign","shortname":":registered:","category":"symbols","emoji_order":"2104","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"tm":{"unicode":"2122","unicode_alt":"2122-fe0f","code_decimal":"™","name":"trade mark sign","shortname":":tm:","category":"symbols","emoji_order":"2105","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"hash":{"unicode":"0023-20e3","unicode_alt":"0023-fe0f-20e3","code_decimal":"#⃣","name":"keycap number sign","shortname":":hash:","category":"symbols","emoji_order":"2106","aliases":[],"aliases_ascii":[],"keywords":["number","symbol"]},"asterisk":{"unicode":"002a-20e3","unicode_alt":"002a-fe0f-20e3","code_decimal":"*⃣","name":"keycap asterisk","shortname":":asterisk:","category":"symbols","emoji_order":"2107","aliases":[":keycap_asterisk:"],"aliases_ascii":[],"keywords":["symbol"]},"zero":{"unicode":"0030-20e3","unicode_alt":"0030-fe0f-20e3","code_decimal":"0⃣","name":"keycap digit zero","shortname":":zero:","category":"symbols","emoji_order":"2108","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"one":{"unicode":"0031-20e3","unicode_alt":"0031-fe0f-20e3","code_decimal":"1⃣","name":"keycap digit one","shortname":":one:","category":"symbols","emoji_order":"2109","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"two":{"unicode":"0032-20e3","unicode_alt":"0032-fe0f-20e3","code_decimal":"2⃣","name":"keycap digit two","shortname":":two:","category":"symbols","emoji_order":"2110","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"three":{"unicode":"0033-20e3","unicode_alt":"0033-fe0f-20e3","code_decimal":"3⃣","name":"keycap digit three","shortname":":three:","category":"symbols","emoji_order":"2111","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"four":{"unicode":"0034-20e3","unicode_alt":"0034-fe0f-20e3","code_decimal":"4⃣","name":"keycap digit four","shortname":":four:","category":"symbols","emoji_order":"2112","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"five":{"unicode":"0035-20e3","unicode_alt":"0035-fe0f-20e3","code_decimal":"5⃣","name":"keycap digit five","shortname":":five:","category":"symbols","emoji_order":"2113","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"six":{"unicode":"0036-20e3","unicode_alt":"0036-fe0f-20e3","code_decimal":"6⃣","name":"keycap digit six","shortname":":six:","category":"symbols","emoji_order":"2114","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"seven":{"unicode":"0037-20e3","unicode_alt":"0037-fe0f-20e3","code_decimal":"7⃣","name":"keycap digit seven","shortname":":seven:","category":"symbols","emoji_order":"2115","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"eight":{"unicode":"0038-20e3","unicode_alt":"0038-fe0f-20e3","code_decimal":"8⃣","name":"keycap digit eight","shortname":":eight:","category":"symbols","emoji_order":"2116","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"nine":{"unicode":"0039-20e3","unicode_alt":"0039-fe0f-20e3","code_decimal":"9⃣","name":"keycap digit nine","shortname":":nine:","category":"symbols","emoji_order":"2117","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"keycap_ten":{"unicode":"1f51f","unicode_alt":"","code_decimal":"🔟","name":"keycap ten","shortname":":keycap_ten:","category":"symbols","emoji_order":"2118","aliases":[],"aliases_ascii":[],"keywords":["number","math","symbol"]},"100":{"unicode":"1f4af","unicode_alt":"","code_decimal":"💯","name":"hundred points symbol","shortname":":100:","category":"symbols","emoji_order":"2119","aliases":[],"aliases_ascii":[],"keywords":["symbol","wow","win","perfect","parties"]},"capital_abcd":{"unicode":"1f520","unicode_alt":"","code_decimal":"🔠","name":"input symbol for latin capital letters","shortname":":capital_abcd:","category":"symbols","emoji_order":"2120","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"abcd":{"unicode":"1f521","unicode_alt":"","code_decimal":"🔡","name":"input symbol for latin small letters","shortname":":abcd:","category":"symbols","emoji_order":"2121","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"1234":{"unicode":"1f522","unicode_alt":"","code_decimal":"🔢","name":"input symbol for numbers","shortname":":1234:","category":"symbols","emoji_order":"2122","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"symbols":{"unicode":"1f523","unicode_alt":"","code_decimal":"🔣","name":"input symbol for symbols","shortname":":symbols:","category":"symbols","emoji_order":"2123","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"abc":{"unicode":"1f524","unicode_alt":"","code_decimal":"🔤","name":"input symbol for latin letters","shortname":":abc:","category":"symbols","emoji_order":"2124","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"a":{"unicode":"1f170","unicode_alt":"","code_decimal":"🅰","name":"negative squared latin capital letter a","shortname":":a:","category":"symbols","emoji_order":"2125","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"ab":{"unicode":"1f18e","unicode_alt":"","code_decimal":"🆎","name":"negative squared ab","shortname":":ab:","category":"symbols","emoji_order":"2126","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"b":{"unicode":"1f171","unicode_alt":"","code_decimal":"🅱","name":"negative squared latin capital letter b","shortname":":b:","category":"symbols","emoji_order":"2127","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"cl":{"unicode":"1f191","unicode_alt":"","code_decimal":"🆑","name":"squared cl","shortname":":cl:","category":"symbols","emoji_order":"2128","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"cool":{"unicode":"1f192","unicode_alt":"","code_decimal":"🆒","name":"squared cool","shortname":":cool:","category":"symbols","emoji_order":"2129","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"free":{"unicode":"1f193","unicode_alt":"","code_decimal":"🆓","name":"squared free","shortname":":free:","category":"symbols","emoji_order":"2130","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"information_source":{"unicode":"2139","unicode_alt":"2139-fe0f","code_decimal":"ℹ","name":"information source","shortname":":information_source:","category":"symbols","emoji_order":"2131","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"id":{"unicode":"1f194","unicode_alt":"","code_decimal":"🆔","name":"squared id","shortname":":id:","category":"symbols","emoji_order":"2132","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"m":{"unicode":"24c2","unicode_alt":"24c2-fe0f","code_decimal":"Ⓜ","name":"circled latin capital letter m","shortname":":m:","category":"symbols","emoji_order":"2133","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"new":{"unicode":"1f195","unicode_alt":"","code_decimal":"🆕","name":"squared new","shortname":":new:","category":"symbols","emoji_order":"2134","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"ng":{"unicode":"1f196","unicode_alt":"","code_decimal":"🆖","name":"squared ng","shortname":":ng:","category":"symbols","emoji_order":"2135","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"o2":{"unicode":"1f17e","unicode_alt":"","code_decimal":"🅾","name":"negative squared latin capital letter o","shortname":":o2:","category":"symbols","emoji_order":"2136","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"ok":{"unicode":"1f197","unicode_alt":"","code_decimal":"🆗","name":"squared ok","shortname":":ok:","category":"symbols","emoji_order":"2137","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"parking":{"unicode":"1f17f","unicode_alt":"1f17f-fe0f","code_decimal":"🅿","name":"negative squared latin capital letter p","shortname":":parking:","category":"symbols","emoji_order":"2138","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"sos":{"unicode":"1f198","unicode_alt":"","code_decimal":"🆘","name":"squared sos","shortname":":sos:","category":"symbols","emoji_order":"2139","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"up":{"unicode":"1f199","unicode_alt":"","code_decimal":"🆙","name":"squared up with exclamation mark","shortname":":up:","category":"symbols","emoji_order":"2140","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"vs":{"unicode":"1f19a","unicode_alt":"","code_decimal":"🆚","name":"squared vs","shortname":":vs:","category":"symbols","emoji_order":"2141","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"koko":{"unicode":"1f201","unicode_alt":"","code_decimal":"🈁","name":"squared katakana koko","shortname":":koko:","category":"symbols","emoji_order":"2142","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"sa":{"unicode":"1f202","unicode_alt":"1f202-fe0f","code_decimal":"🈂","name":"squared katakana sa","shortname":":sa:","category":"symbols","emoji_order":"2143","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u6708":{"unicode":"1f237","unicode_alt":"1f237-fe0f","code_decimal":"🈷","name":"squared cjk unified ideograph-6708","shortname":":u6708:","category":"symbols","emoji_order":"2144","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u6709":{"unicode":"1f236","unicode_alt":"","code_decimal":"🈶","name":"squared cjk unified ideograph-6709","shortname":":u6709:","category":"symbols","emoji_order":"2145","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u6307":{"unicode":"1f22f","unicode_alt":"1f22f-fe0f","code_decimal":"🈯","name":"squared cjk unified ideograph-6307","shortname":":u6307:","category":"symbols","emoji_order":"2146","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"ideograph_advantage":{"unicode":"1f250","unicode_alt":"","code_decimal":"🉐","name":"circled ideograph advantage","shortname":":ideograph_advantage:","category":"symbols","emoji_order":"2147","aliases":[],"aliases_ascii":[],"keywords":["japan","symbol"]},"u5272":{"unicode":"1f239","unicode_alt":"","code_decimal":"🈹","name":"squared cjk unified ideograph-5272","shortname":":u5272:","category":"symbols","emoji_order":"2148","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u7121":{"unicode":"1f21a","unicode_alt":"1f21a-fe0f","code_decimal":"🈚","name":"squared cjk unified ideograph-7121","shortname":":u7121:","category":"symbols","emoji_order":"2149","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u7981":{"unicode":"1f232","unicode_alt":"","code_decimal":"🈲","name":"squared cjk unified ideograph-7981","shortname":":u7981:","category":"symbols","emoji_order":"2150","aliases":[],"aliases_ascii":[],"keywords":["japan","symbol"]},"accept":{"unicode":"1f251","unicode_alt":"","code_decimal":"🉑","name":"circled ideograph accept","shortname":":accept:","category":"symbols","emoji_order":"2151","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u7533":{"unicode":"1f238","unicode_alt":"","code_decimal":"🈸","name":"squared cjk unified ideograph-7533","shortname":":u7533:","category":"symbols","emoji_order":"2152","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u5408":{"unicode":"1f234","unicode_alt":"","code_decimal":"🈴","name":"squared cjk unified ideograph-5408","shortname":":u5408:","category":"symbols","emoji_order":"2153","aliases":[],"aliases_ascii":[],"keywords":["japan","symbol"]},"u7a7a":{"unicode":"1f233","unicode_alt":"","code_decimal":"🈳","name":"squared cjk unified ideograph-7a7a","shortname":":u7a7a:","category":"symbols","emoji_order":"2154","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"congratulations":{"unicode":"3297","unicode_alt":"3297-fe0f","code_decimal":"㊗","name":"circled ideograph congratulation","shortname":":congratulations:","category":"symbols","emoji_order":"2155","aliases":[],"aliases_ascii":[],"keywords":["japan","symbol"]},"secret":{"unicode":"3299","unicode_alt":"3299-fe0f","code_decimal":"㊙","name":"circled ideograph secret","shortname":":secret:","category":"symbols","emoji_order":"2156","aliases":[],"aliases_ascii":[],"keywords":["japan","symbol"]},"u55b6":{"unicode":"1f23a","unicode_alt":"","code_decimal":"🈺","name":"squared cjk unified ideograph-55b6","shortname":":u55b6:","category":"symbols","emoji_order":"2157","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"u6e80":{"unicode":"1f235","unicode_alt":"","code_decimal":"🈵","name":"squared cjk unified ideograph-6e80","shortname":":u6e80:","category":"symbols","emoji_order":"2158","aliases":[],"aliases_ascii":[],"keywords":["japan","symbol"]},"black_small_square":{"unicode":"25aa","unicode_alt":"25aa-fe0f","code_decimal":"▪","name":"black small square","shortname":":black_small_square:","category":"symbols","emoji_order":"2159","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"white_small_square":{"unicode":"25ab","unicode_alt":"25ab-fe0f","code_decimal":"▫","name":"white small square","shortname":":white_small_square:","category":"symbols","emoji_order":"2160","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"white_medium_square":{"unicode":"25fb","unicode_alt":"25fb-fe0f","code_decimal":"◻","name":"white medium square","shortname":":white_medium_square:","category":"symbols","emoji_order":"2161","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"black_medium_square":{"unicode":"25fc","unicode_alt":"25fc-fe0f","code_decimal":"◼","name":"black medium square","shortname":":black_medium_square:","category":"symbols","emoji_order":"2162","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"white_medium_small_square":{"unicode":"25fd","unicode_alt":"25fd-fe0f","code_decimal":"◽","name":"white medium small square","shortname":":white_medium_small_square:","category":"symbols","emoji_order":"2163","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"black_medium_small_square":{"unicode":"25fe","unicode_alt":"25fe-fe0f","code_decimal":"◾","name":"black medium small square","shortname":":black_medium_small_square:","category":"symbols","emoji_order":"2164","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"black_large_square":{"unicode":"2b1b","unicode_alt":"2b1b-fe0f","code_decimal":"⬛","name":"black large square","shortname":":black_large_square:","category":"symbols","emoji_order":"2165","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"white_large_square":{"unicode":"2b1c","unicode_alt":"2b1c-fe0f","code_decimal":"⬜","name":"white large square","shortname":":white_large_square:","category":"symbols","emoji_order":"2166","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"large_orange_diamond":{"unicode":"1f536","unicode_alt":"","code_decimal":"🔶","name":"large orange diamond","shortname":":large_orange_diamond:","category":"symbols","emoji_order":"2167","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol"]},"large_blue_diamond":{"unicode":"1f537","unicode_alt":"","code_decimal":"🔷","name":"large blue diamond","shortname":":large_blue_diamond:","category":"symbols","emoji_order":"2168","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol"]},"small_orange_diamond":{"unicode":"1f538","unicode_alt":"","code_decimal":"🔸","name":"small orange diamond","shortname":":small_orange_diamond:","category":"symbols","emoji_order":"2169","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol"]},"small_blue_diamond":{"unicode":"1f539","unicode_alt":"","code_decimal":"🔹","name":"small blue diamond","shortname":":small_blue_diamond:","category":"symbols","emoji_order":"2170","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol"]},"small_red_triangle":{"unicode":"1f53a","unicode_alt":"","code_decimal":"🔺","name":"up-pointing red triangle","shortname":":small_red_triangle:","category":"symbols","emoji_order":"2171","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","triangle"]},"small_red_triangle_down":{"unicode":"1f53b","unicode_alt":"","code_decimal":"🔻","name":"down-pointing red triangle","shortname":":small_red_triangle_down:","category":"symbols","emoji_order":"2172","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","triangle"]},"diamond_shape_with_a_dot_inside":{"unicode":"1f4a0","unicode_alt":"","code_decimal":"💠","name":"diamond shape with a dot inside","shortname":":diamond_shape_with_a_dot_inside:","category":"symbols","emoji_order":"2173","aliases":[],"aliases_ascii":[],"keywords":["symbol"]},"radio_button":{"unicode":"1f518","unicode_alt":"","code_decimal":"🔘","name":"radio button","shortname":":radio_button:","category":"symbols","emoji_order":"2174","aliases":[],"aliases_ascii":[],"keywords":["symbol","circle"]},"black_square_button":{"unicode":"1f532","unicode_alt":"","code_decimal":"🔲","name":"black square button","shortname":":black_square_button:","category":"symbols","emoji_order":"2175","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"white_square_button":{"unicode":"1f533","unicode_alt":"","code_decimal":"🔳","name":"white square button","shortname":":white_square_button:","category":"symbols","emoji_order":"2176","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","square"]},"white_circle":{"unicode":"26aa","unicode_alt":"26aa-fe0f","code_decimal":"⚪","name":"white circle","shortname":":white_circle:","category":"symbols","emoji_order":"2177","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","circle"]},"black_circle":{"unicode":"26ab","unicode_alt":"26ab-fe0f","code_decimal":"⚫","name":"black circle","shortname":":black_circle:","category":"symbols","emoji_order":"2178","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","circle"]},"red_circle":{"unicode":"1f534","unicode_alt":"","code_decimal":"🔴","name":"red circle","shortname":":red_circle:","category":"symbols","emoji_order":"2179","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","circle"]},"blue_circle":{"unicode":"1f535","unicode_alt":"","code_decimal":"🔵","name":"blue circle","shortname":":blue_circle:","category":"symbols","emoji_order":"2180","aliases":[],"aliases_ascii":[],"keywords":["shapes","symbol","circle"]},"checkered_flag":{"unicode":"1f3c1","unicode_alt":"","code_decimal":"🏁","name":"chequered flag","shortname":":checkered_flag:","category":"travel","emoji_order":"2181","aliases":[],"aliases_ascii":[],"keywords":["object"]},"triangular_flag_on_post":{"unicode":"1f6a9","unicode_alt":"","code_decimal":"🚩","name":"triangular flag on post","shortname":":triangular_flag_on_post:","category":"objects","emoji_order":"2182","aliases":[],"aliases_ascii":[],"keywords":["object"]},"crossed_flags":{"unicode":"1f38c","unicode_alt":"","code_decimal":"🎌","name":"crossed flags","shortname":":crossed_flags:","category":"objects","emoji_order":"2183","aliases":[],"aliases_ascii":[],"keywords":["object","japan"]},"flag_black":{"unicode":"1f3f4","unicode_alt":"","code_decimal":"🏴","name":"waving black flag","shortname":":flag_black:","category":"objects","emoji_order":"2184","aliases":[":waving_black_flag:"],"aliases_ascii":[],"keywords":["object"]},"flag_white":{"unicode":"1f3f3","unicode_alt":"1f3f3-fe0f","code_decimal":"🏳","name":"waving white flag","shortname":":flag_white:","category":"objects","emoji_order":"2185","aliases":[":waving_white_flag:"],"aliases_ascii":[],"keywords":["object"]},"rainbow_flag":{"unicode":"1f3f3-1f308","unicode_alt":"","code_decimal":"🏳🌈","name":"rainbow_flag","shortname":":rainbow_flag:","category":"objects","emoji_order":"2186","aliases":[":gay_pride_flag:"],"aliases_ascii":[],"keywords":[]},"flag_ac":{"unicode":"1f1e6-1f1e8","unicode_alt":"","code_decimal":"🇦🇨","name":"ascension","shortname":":flag_ac:","category":"flags","emoji_order":"2187","aliases":[":ac:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ad":{"unicode":"1f1e6-1f1e9","unicode_alt":"","code_decimal":"🇦🇩","name":"andorra","shortname":":flag_ad:","category":"flags","emoji_order":"2188","aliases":[":ad:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ae":{"unicode":"1f1e6-1f1ea","unicode_alt":"","code_decimal":"🇦🇪","name":"the united arab emirates","shortname":":flag_ae:","category":"flags","emoji_order":"2189","aliases":[":ae:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_af":{"unicode":"1f1e6-1f1eb","unicode_alt":"","code_decimal":"🇦🇫","name":"afghanistan","shortname":":flag_af:","category":"flags","emoji_order":"2190","aliases":[":af:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ag":{"unicode":"1f1e6-1f1ec","unicode_alt":"","code_decimal":"🇦🇬","name":"antigua and barbuda","shortname":":flag_ag:","category":"flags","emoji_order":"2191","aliases":[":ag:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ai":{"unicode":"1f1e6-1f1ee","unicode_alt":"","code_decimal":"🇦🇮","name":"anguilla","shortname":":flag_ai:","category":"flags","emoji_order":"2192","aliases":[":ai:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_al":{"unicode":"1f1e6-1f1f1","unicode_alt":"","code_decimal":"🇦🇱","name":"albania","shortname":":flag_al:","category":"flags","emoji_order":"2193","aliases":[":al:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_am":{"unicode":"1f1e6-1f1f2","unicode_alt":"","code_decimal":"🇦🇲","name":"armenia","shortname":":flag_am:","category":"flags","emoji_order":"2194","aliases":[":am:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ao":{"unicode":"1f1e6-1f1f4","unicode_alt":"","code_decimal":"🇦🇴","name":"angola","shortname":":flag_ao:","category":"flags","emoji_order":"2195","aliases":[":ao:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_aq":{"unicode":"1f1e6-1f1f6","unicode_alt":"","code_decimal":"🇦🇶","name":"antarctica","shortname":":flag_aq:","category":"flags","emoji_order":"2196","aliases":[":aq:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ar":{"unicode":"1f1e6-1f1f7","unicode_alt":"","code_decimal":"🇦🇷","name":"argentina","shortname":":flag_ar:","category":"flags","emoji_order":"2197","aliases":[":ar:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_as":{"unicode":"1f1e6-1f1f8","unicode_alt":"","code_decimal":"🇦🇸","name":"american samoa","shortname":":flag_as:","category":"flags","emoji_order":"2198","aliases":[":as:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_at":{"unicode":"1f1e6-1f1f9","unicode_alt":"","code_decimal":"🇦🇹","name":"austria","shortname":":flag_at:","category":"flags","emoji_order":"2199","aliases":[":at:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_au":{"unicode":"1f1e6-1f1fa","unicode_alt":"","code_decimal":"🇦🇺","name":"australia","shortname":":flag_au:","category":"flags","emoji_order":"2200","aliases":[":au:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_aw":{"unicode":"1f1e6-1f1fc","unicode_alt":"","code_decimal":"🇦🇼","name":"aruba","shortname":":flag_aw:","category":"flags","emoji_order":"2201","aliases":[":aw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ax":{"unicode":"1f1e6-1f1fd","unicode_alt":"","code_decimal":"🇦🇽","name":"\u00e5land islands","shortname":":flag_ax:","category":"flags","emoji_order":"2202","aliases":[":ax:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_az":{"unicode":"1f1e6-1f1ff","unicode_alt":"","code_decimal":"🇦🇿","name":"azerbaijan","shortname":":flag_az:","category":"flags","emoji_order":"2203","aliases":[":az:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ba":{"unicode":"1f1e7-1f1e6","unicode_alt":"","code_decimal":"🇧🇦","name":"bosnia and herzegovina","shortname":":flag_ba:","category":"flags","emoji_order":"2204","aliases":[":ba:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bb":{"unicode":"1f1e7-1f1e7","unicode_alt":"","code_decimal":"🇧🇧","name":"barbados","shortname":":flag_bb:","category":"flags","emoji_order":"2205","aliases":[":bb:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bd":{"unicode":"1f1e7-1f1e9","unicode_alt":"","code_decimal":"🇧🇩","name":"bangladesh","shortname":":flag_bd:","category":"flags","emoji_order":"2206","aliases":[":bd:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_be":{"unicode":"1f1e7-1f1ea","unicode_alt":"","code_decimal":"🇧🇪","name":"belgium","shortname":":flag_be:","category":"flags","emoji_order":"2207","aliases":[":be:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bf":{"unicode":"1f1e7-1f1eb","unicode_alt":"","code_decimal":"🇧🇫","name":"burkina faso","shortname":":flag_bf:","category":"flags","emoji_order":"2208","aliases":[":bf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bg":{"unicode":"1f1e7-1f1ec","unicode_alt":"","code_decimal":"🇧🇬","name":"bulgaria","shortname":":flag_bg:","category":"flags","emoji_order":"2209","aliases":[":bg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bh":{"unicode":"1f1e7-1f1ed","unicode_alt":"","code_decimal":"🇧🇭","name":"bahrain","shortname":":flag_bh:","category":"flags","emoji_order":"2210","aliases":[":bh:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bi":{"unicode":"1f1e7-1f1ee","unicode_alt":"","code_decimal":"🇧🇮","name":"burundi","shortname":":flag_bi:","category":"flags","emoji_order":"2211","aliases":[":bi:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bj":{"unicode":"1f1e7-1f1ef","unicode_alt":"","code_decimal":"🇧🇯","name":"benin","shortname":":flag_bj:","category":"flags","emoji_order":"2212","aliases":[":bj:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bl":{"unicode":"1f1e7-1f1f1","unicode_alt":"","code_decimal":"🇧🇱","name":"saint barth\u00e9lemy","shortname":":flag_bl:","category":"flags","emoji_order":"2213","aliases":[":bl:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bm":{"unicode":"1f1e7-1f1f2","unicode_alt":"","code_decimal":"🇧🇲","name":"bermuda","shortname":":flag_bm:","category":"flags","emoji_order":"2214","aliases":[":bm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bn":{"unicode":"1f1e7-1f1f3","unicode_alt":"","code_decimal":"🇧🇳","name":"brunei","shortname":":flag_bn:","category":"flags","emoji_order":"2215","aliases":[":bn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bo":{"unicode":"1f1e7-1f1f4","unicode_alt":"","code_decimal":"🇧🇴","name":"bolivia","shortname":":flag_bo:","category":"flags","emoji_order":"2216","aliases":[":bo:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bq":{"unicode":"1f1e7-1f1f6","unicode_alt":"","code_decimal":"🇧🇶","name":"caribbean netherlands","shortname":":flag_bq:","category":"flags","emoji_order":"2217","aliases":[":bq:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_br":{"unicode":"1f1e7-1f1f7","unicode_alt":"","code_decimal":"🇧🇷","name":"brazil","shortname":":flag_br:","category":"flags","emoji_order":"2218","aliases":[":br:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bs":{"unicode":"1f1e7-1f1f8","unicode_alt":"","code_decimal":"🇧🇸","name":"the bahamas","shortname":":flag_bs:","category":"flags","emoji_order":"2219","aliases":[":bs:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bt":{"unicode":"1f1e7-1f1f9","unicode_alt":"","code_decimal":"🇧🇹","name":"bhutan","shortname":":flag_bt:","category":"flags","emoji_order":"2220","aliases":[":bt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bv":{"unicode":"1f1e7-1f1fb","unicode_alt":"","code_decimal":"🇧🇻","name":"bouvet island","shortname":":flag_bv:","category":"flags","emoji_order":"2221","aliases":[":bv:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bw":{"unicode":"1f1e7-1f1fc","unicode_alt":"","code_decimal":"🇧🇼","name":"botswana","shortname":":flag_bw:","category":"flags","emoji_order":"2222","aliases":[":bw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_by":{"unicode":"1f1e7-1f1fe","unicode_alt":"","code_decimal":"🇧🇾","name":"belarus","shortname":":flag_by:","category":"flags","emoji_order":"2223","aliases":[":by:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_bz":{"unicode":"1f1e7-1f1ff","unicode_alt":"","code_decimal":"🇧🇿","name":"belize","shortname":":flag_bz:","category":"flags","emoji_order":"2224","aliases":[":bz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ca":{"unicode":"1f1e8-1f1e6","unicode_alt":"","code_decimal":"🇨🇦","name":"canada","shortname":":flag_ca:","category":"flags","emoji_order":"2225","aliases":[":ca:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cc":{"unicode":"1f1e8-1f1e8","unicode_alt":"","code_decimal":"🇨🇨","name":"cocos (keeling) islands","shortname":":flag_cc:","category":"flags","emoji_order":"2226","aliases":[":cc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cd":{"unicode":"1f1e8-1f1e9","unicode_alt":"","code_decimal":"🇨🇩","name":"the democratic republic of the congo","shortname":":flag_cd:","category":"flags","emoji_order":"2227","aliases":[":congo:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cf":{"unicode":"1f1e8-1f1eb","unicode_alt":"","code_decimal":"🇨🇫","name":"central african republic","shortname":":flag_cf:","category":"flags","emoji_order":"2228","aliases":[":cf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cg":{"unicode":"1f1e8-1f1ec","unicode_alt":"","code_decimal":"🇨🇬","name":"the republic of the congo","shortname":":flag_cg:","category":"flags","emoji_order":"2229","aliases":[":cg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ch":{"unicode":"1f1e8-1f1ed","unicode_alt":"","code_decimal":"🇨🇭","name":"switzerland","shortname":":flag_ch:","category":"flags","emoji_order":"2230","aliases":[":ch:"],"aliases_ascii":[],"keywords":["country","neutral","flag"]},"flag_ci":{"unicode":"1f1e8-1f1ee","unicode_alt":"","code_decimal":"🇨🇮","name":"c\u00f4te d\u2019ivoire","shortname":":flag_ci:","category":"flags","emoji_order":"2231","aliases":[":ci:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ck":{"unicode":"1f1e8-1f1f0","unicode_alt":"","code_decimal":"🇨🇰","name":"cook islands","shortname":":flag_ck:","category":"flags","emoji_order":"2232","aliases":[":ck:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cl":{"unicode":"1f1e8-1f1f1","unicode_alt":"","code_decimal":"🇨🇱","name":"chile","shortname":":flag_cl:","category":"flags","emoji_order":"2233","aliases":[":chile:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cm":{"unicode":"1f1e8-1f1f2","unicode_alt":"","code_decimal":"🇨🇲","name":"cameroon","shortname":":flag_cm:","category":"flags","emoji_order":"2234","aliases":[":cm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cn":{"unicode":"1f1e8-1f1f3","unicode_alt":"","code_decimal":"🇨🇳","name":"china","shortname":":flag_cn:","category":"flags","emoji_order":"2235","aliases":[":cn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_co":{"unicode":"1f1e8-1f1f4","unicode_alt":"","code_decimal":"🇨🇴","name":"colombia","shortname":":flag_co:","category":"flags","emoji_order":"2236","aliases":[":co:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cp":{"unicode":"1f1e8-1f1f5","unicode_alt":"","code_decimal":"🇨🇵","name":"clipperton island","shortname":":flag_cp:","category":"flags","emoji_order":"2237","aliases":[":cp:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cr":{"unicode":"1f1e8-1f1f7","unicode_alt":"","code_decimal":"🇨🇷","name":"costa rica","shortname":":flag_cr:","category":"flags","emoji_order":"2238","aliases":[":cr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cu":{"unicode":"1f1e8-1f1fa","unicode_alt":"","code_decimal":"🇨🇺","name":"cuba","shortname":":flag_cu:","category":"flags","emoji_order":"2239","aliases":[":cu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cv":{"unicode":"1f1e8-1f1fb","unicode_alt":"","code_decimal":"🇨🇻","name":"cape verde","shortname":":flag_cv:","category":"flags","emoji_order":"2240","aliases":[":cv:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cw":{"unicode":"1f1e8-1f1fc","unicode_alt":"","code_decimal":"🇨🇼","name":"cura\u00e7ao","shortname":":flag_cw:","category":"flags","emoji_order":"2241","aliases":[":cw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cx":{"unicode":"1f1e8-1f1fd","unicode_alt":"","code_decimal":"🇨🇽","name":"christmas island","shortname":":flag_cx:","category":"flags","emoji_order":"2242","aliases":[":cx:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cy":{"unicode":"1f1e8-1f1fe","unicode_alt":"","code_decimal":"🇨🇾","name":"cyprus","shortname":":flag_cy:","category":"flags","emoji_order":"2243","aliases":[":cy:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_cz":{"unicode":"1f1e8-1f1ff","unicode_alt":"","code_decimal":"🇨🇿","name":"the czech republic","shortname":":flag_cz:","category":"flags","emoji_order":"2244","aliases":[":cz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_de":{"unicode":"1f1e9-1f1ea","unicode_alt":"","code_decimal":"🇩🇪","name":"germany","shortname":":flag_de:","category":"flags","emoji_order":"2245","aliases":[":de:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_dg":{"unicode":"1f1e9-1f1ec","unicode_alt":"","code_decimal":"🇩🇬","name":"diego garcia","shortname":":flag_dg:","category":"flags","emoji_order":"2246","aliases":[":dg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_dj":{"unicode":"1f1e9-1f1ef","unicode_alt":"","code_decimal":"🇩🇯","name":"djibouti","shortname":":flag_dj:","category":"flags","emoji_order":"2247","aliases":[":dj:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_dk":{"unicode":"1f1e9-1f1f0","unicode_alt":"","code_decimal":"🇩🇰","name":"denmark","shortname":":flag_dk:","category":"flags","emoji_order":"2248","aliases":[":dk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_dm":{"unicode":"1f1e9-1f1f2","unicode_alt":"","code_decimal":"🇩🇲","name":"dominica","shortname":":flag_dm:","category":"flags","emoji_order":"2249","aliases":[":dm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_do":{"unicode":"1f1e9-1f1f4","unicode_alt":"","code_decimal":"🇩🇴","name":"the dominican republic","shortname":":flag_do:","category":"flags","emoji_order":"2250","aliases":[":do:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_dz":{"unicode":"1f1e9-1f1ff","unicode_alt":"","code_decimal":"🇩🇿","name":"algeria","shortname":":flag_dz:","category":"flags","emoji_order":"2251","aliases":[":dz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ea":{"unicode":"1f1ea-1f1e6","unicode_alt":"","code_decimal":"🇪🇦","name":"ceuta, melilla","shortname":":flag_ea:","category":"flags","emoji_order":"2252","aliases":[":ea:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ec":{"unicode":"1f1ea-1f1e8","unicode_alt":"","code_decimal":"🇪🇨","name":"ecuador","shortname":":flag_ec:","category":"flags","emoji_order":"2253","aliases":[":ec:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ee":{"unicode":"1f1ea-1f1ea","unicode_alt":"","code_decimal":"🇪🇪","name":"estonia","shortname":":flag_ee:","category":"flags","emoji_order":"2254","aliases":[":ee:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_eg":{"unicode":"1f1ea-1f1ec","unicode_alt":"","code_decimal":"🇪🇬","name":"egypt","shortname":":flag_eg:","category":"flags","emoji_order":"2255","aliases":[":eg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_eh":{"unicode":"1f1ea-1f1ed","unicode_alt":"","code_decimal":"🇪🇭","name":"western sahara","shortname":":flag_eh:","category":"flags","emoji_order":"2256","aliases":[":eh:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_er":{"unicode":"1f1ea-1f1f7","unicode_alt":"","code_decimal":"🇪🇷","name":"eritrea","shortname":":flag_er:","category":"flags","emoji_order":"2257","aliases":[":er:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_es":{"unicode":"1f1ea-1f1f8","unicode_alt":"","code_decimal":"🇪🇸","name":"spain","shortname":":flag_es:","category":"flags","emoji_order":"2258","aliases":[":es:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_et":{"unicode":"1f1ea-1f1f9","unicode_alt":"","code_decimal":"🇪🇹","name":"ethiopia","shortname":":flag_et:","category":"flags","emoji_order":"2259","aliases":[":et:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_eu":{"unicode":"1f1ea-1f1fa","unicode_alt":"","code_decimal":"🇪🇺","name":"european union","shortname":":flag_eu:","category":"flags","emoji_order":"2260","aliases":[":eu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_fi":{"unicode":"1f1eb-1f1ee","unicode_alt":"","code_decimal":"🇫🇮","name":"finland","shortname":":flag_fi:","category":"flags","emoji_order":"2261","aliases":[":fi:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_fj":{"unicode":"1f1eb-1f1ef","unicode_alt":"","code_decimal":"🇫🇯","name":"fiji","shortname":":flag_fj:","category":"flags","emoji_order":"2262","aliases":[":fj:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_fk":{"unicode":"1f1eb-1f1f0","unicode_alt":"","code_decimal":"🇫🇰","name":"falkland islands","shortname":":flag_fk:","category":"flags","emoji_order":"2263","aliases":[":fk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_fm":{"unicode":"1f1eb-1f1f2","unicode_alt":"","code_decimal":"🇫🇲","name":"micronesia","shortname":":flag_fm:","category":"flags","emoji_order":"2264","aliases":[":fm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_fo":{"unicode":"1f1eb-1f1f4","unicode_alt":"","code_decimal":"🇫🇴","name":"faroe islands","shortname":":flag_fo:","category":"flags","emoji_order":"2265","aliases":[":fo:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_fr":{"unicode":"1f1eb-1f1f7","unicode_alt":"","code_decimal":"🇫🇷","name":"france","shortname":":flag_fr:","category":"flags","emoji_order":"2266","aliases":[":fr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ga":{"unicode":"1f1ec-1f1e6","unicode_alt":"","code_decimal":"🇬🇦","name":"gabon","shortname":":flag_ga:","category":"flags","emoji_order":"2267","aliases":[":ga:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gb":{"unicode":"1f1ec-1f1e7","unicode_alt":"","code_decimal":"🇬🇧","name":"great britain","shortname":":flag_gb:","category":"flags","emoji_order":"2268","aliases":[":gb:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gd":{"unicode":"1f1ec-1f1e9","unicode_alt":"","code_decimal":"🇬🇩","name":"grenada","shortname":":flag_gd:","category":"flags","emoji_order":"2269","aliases":[":gd:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ge":{"unicode":"1f1ec-1f1ea","unicode_alt":"","code_decimal":"🇬🇪","name":"georgia","shortname":":flag_ge:","category":"flags","emoji_order":"2270","aliases":[":ge:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gf":{"unicode":"1f1ec-1f1eb","unicode_alt":"","code_decimal":"🇬🇫","name":"french guiana","shortname":":flag_gf:","category":"flags","emoji_order":"2271","aliases":[":gf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gg":{"unicode":"1f1ec-1f1ec","unicode_alt":"","code_decimal":"🇬🇬","name":"guernsey","shortname":":flag_gg:","category":"flags","emoji_order":"2272","aliases":[":gg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gh":{"unicode":"1f1ec-1f1ed","unicode_alt":"","code_decimal":"🇬🇭","name":"ghana","shortname":":flag_gh:","category":"flags","emoji_order":"2273","aliases":[":gh:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gi":{"unicode":"1f1ec-1f1ee","unicode_alt":"","code_decimal":"🇬🇮","name":"gibraltar","shortname":":flag_gi:","category":"flags","emoji_order":"2274","aliases":[":gi:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gl":{"unicode":"1f1ec-1f1f1","unicode_alt":"","code_decimal":"🇬🇱","name":"greenland","shortname":":flag_gl:","category":"flags","emoji_order":"2275","aliases":[":gl:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gm":{"unicode":"1f1ec-1f1f2","unicode_alt":"","code_decimal":"🇬🇲","name":"the gambia","shortname":":flag_gm:","category":"flags","emoji_order":"2276","aliases":[":gm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gn":{"unicode":"1f1ec-1f1f3","unicode_alt":"","code_decimal":"🇬🇳","name":"guinea","shortname":":flag_gn:","category":"flags","emoji_order":"2277","aliases":[":gn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gp":{"unicode":"1f1ec-1f1f5","unicode_alt":"","code_decimal":"🇬🇵","name":"guadeloupe","shortname":":flag_gp:","category":"flags","emoji_order":"2278","aliases":[":gp:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gq":{"unicode":"1f1ec-1f1f6","unicode_alt":"","code_decimal":"🇬🇶","name":"equatorial guinea","shortname":":flag_gq:","category":"flags","emoji_order":"2279","aliases":[":gq:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gr":{"unicode":"1f1ec-1f1f7","unicode_alt":"","code_decimal":"🇬🇷","name":"greece","shortname":":flag_gr:","category":"flags","emoji_order":"2280","aliases":[":gr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gs":{"unicode":"1f1ec-1f1f8","unicode_alt":"","code_decimal":"🇬🇸","name":"south georgia","shortname":":flag_gs:","category":"flags","emoji_order":"2281","aliases":[":gs:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gt":{"unicode":"1f1ec-1f1f9","unicode_alt":"","code_decimal":"🇬🇹","name":"guatemala","shortname":":flag_gt:","category":"flags","emoji_order":"2282","aliases":[":gt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gu":{"unicode":"1f1ec-1f1fa","unicode_alt":"","code_decimal":"🇬🇺","name":"guam","shortname":":flag_gu:","category":"flags","emoji_order":"2283","aliases":[":gu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gw":{"unicode":"1f1ec-1f1fc","unicode_alt":"","code_decimal":"🇬🇼","name":"guinea-bissau","shortname":":flag_gw:","category":"flags","emoji_order":"2284","aliases":[":gw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_gy":{"unicode":"1f1ec-1f1fe","unicode_alt":"","code_decimal":"🇬🇾","name":"guyana","shortname":":flag_gy:","category":"flags","emoji_order":"2285","aliases":[":gy:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_hk":{"unicode":"1f1ed-1f1f0","unicode_alt":"","code_decimal":"🇭🇰","name":"hong kong","shortname":":flag_hk:","category":"flags","emoji_order":"2286","aliases":[":hk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_hm":{"unicode":"1f1ed-1f1f2","unicode_alt":"","code_decimal":"🇭🇲","name":"heard island and mcdonald islands","shortname":":flag_hm:","category":"flags","emoji_order":"2287","aliases":[":hm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_hn":{"unicode":"1f1ed-1f1f3","unicode_alt":"","code_decimal":"🇭🇳","name":"honduras","shortname":":flag_hn:","category":"flags","emoji_order":"2288","aliases":[":hn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_hr":{"unicode":"1f1ed-1f1f7","unicode_alt":"","code_decimal":"🇭🇷","name":"croatia","shortname":":flag_hr:","category":"flags","emoji_order":"2289","aliases":[":hr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ht":{"unicode":"1f1ed-1f1f9","unicode_alt":"","code_decimal":"🇭🇹","name":"haiti","shortname":":flag_ht:","category":"flags","emoji_order":"2290","aliases":[":ht:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_hu":{"unicode":"1f1ed-1f1fa","unicode_alt":"","code_decimal":"🇭🇺","name":"hungary","shortname":":flag_hu:","category":"flags","emoji_order":"2291","aliases":[":hu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ic":{"unicode":"1f1ee-1f1e8","unicode_alt":"","code_decimal":"🇮🇨","name":"canary islands","shortname":":flag_ic:","category":"flags","emoji_order":"2292","aliases":[":ic:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_id":{"unicode":"1f1ee-1f1e9","unicode_alt":"","code_decimal":"🇮🇩","name":"indonesia","shortname":":flag_id:","category":"flags","emoji_order":"2293","aliases":[":indonesia:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ie":{"unicode":"1f1ee-1f1ea","unicode_alt":"","code_decimal":"🇮🇪","name":"ireland","shortname":":flag_ie:","category":"flags","emoji_order":"2294","aliases":[":ie:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_il":{"unicode":"1f1ee-1f1f1","unicode_alt":"","code_decimal":"🇮🇱","name":"israel","shortname":":flag_il:","category":"flags","emoji_order":"2295","aliases":[":il:"],"aliases_ascii":[],"keywords":["jew","country","flag"]},"flag_im":{"unicode":"1f1ee-1f1f2","unicode_alt":"","code_decimal":"🇮🇲","name":"isle of man","shortname":":flag_im:","category":"flags","emoji_order":"2296","aliases":[":im:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_in":{"unicode":"1f1ee-1f1f3","unicode_alt":"","code_decimal":"🇮🇳","name":"india","shortname":":flag_in:","category":"flags","emoji_order":"2297","aliases":[":in:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_io":{"unicode":"1f1ee-1f1f4","unicode_alt":"","code_decimal":"🇮🇴","name":"british indian ocean territory","shortname":":flag_io:","category":"flags","emoji_order":"2298","aliases":[":io:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_iq":{"unicode":"1f1ee-1f1f6","unicode_alt":"","code_decimal":"🇮🇶","name":"iraq","shortname":":flag_iq:","category":"flags","emoji_order":"2299","aliases":[":iq:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ir":{"unicode":"1f1ee-1f1f7","unicode_alt":"","code_decimal":"🇮🇷","name":"iran","shortname":":flag_ir:","category":"flags","emoji_order":"2300","aliases":[":ir:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_is":{"unicode":"1f1ee-1f1f8","unicode_alt":"","code_decimal":"🇮🇸","name":"iceland","shortname":":flag_is:","category":"flags","emoji_order":"2301","aliases":[":is:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_it":{"unicode":"1f1ee-1f1f9","unicode_alt":"","code_decimal":"🇮🇹","name":"italy","shortname":":flag_it:","category":"flags","emoji_order":"2302","aliases":[":it:"],"aliases_ascii":[],"keywords":["italian","country","flag"]},"flag_je":{"unicode":"1f1ef-1f1ea","unicode_alt":"","code_decimal":"🇯🇪","name":"jersey","shortname":":flag_je:","category":"flags","emoji_order":"2303","aliases":[":je:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_jm":{"unicode":"1f1ef-1f1f2","unicode_alt":"","code_decimal":"🇯🇲","name":"jamaica","shortname":":flag_jm:","category":"flags","emoji_order":"2304","aliases":[":jm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_jo":{"unicode":"1f1ef-1f1f4","unicode_alt":"","code_decimal":"🇯🇴","name":"jordan","shortname":":flag_jo:","category":"flags","emoji_order":"2305","aliases":[":jo:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_jp":{"unicode":"1f1ef-1f1f5","unicode_alt":"","code_decimal":"🇯🇵","name":"japan","shortname":":flag_jp:","category":"flags","emoji_order":"2306","aliases":[":jp:"],"aliases_ascii":[],"keywords":["japan","country","flag"]},"flag_ke":{"unicode":"1f1f0-1f1ea","unicode_alt":"","code_decimal":"🇰🇪","name":"kenya","shortname":":flag_ke:","category":"flags","emoji_order":"2307","aliases":[":ke:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kg":{"unicode":"1f1f0-1f1ec","unicode_alt":"","code_decimal":"🇰🇬","name":"kyrgyzstan","shortname":":flag_kg:","category":"flags","emoji_order":"2308","aliases":[":kg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kh":{"unicode":"1f1f0-1f1ed","unicode_alt":"","code_decimal":"🇰🇭","name":"cambodia","shortname":":flag_kh:","category":"flags","emoji_order":"2309","aliases":[":kh:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ki":{"unicode":"1f1f0-1f1ee","unicode_alt":"","code_decimal":"🇰🇮","name":"kiribati","shortname":":flag_ki:","category":"flags","emoji_order":"2310","aliases":[":ki:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_km":{"unicode":"1f1f0-1f1f2","unicode_alt":"","code_decimal":"🇰🇲","name":"the comoros","shortname":":flag_km:","category":"flags","emoji_order":"2311","aliases":[":km:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kn":{"unicode":"1f1f0-1f1f3","unicode_alt":"","code_decimal":"🇰🇳","name":"saint kitts and nevis","shortname":":flag_kn:","category":"flags","emoji_order":"2312","aliases":[":kn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kp":{"unicode":"1f1f0-1f1f5","unicode_alt":"","code_decimal":"🇰🇵","name":"north korea","shortname":":flag_kp:","category":"flags","emoji_order":"2313","aliases":[":kp:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kr":{"unicode":"1f1f0-1f1f7","unicode_alt":"","code_decimal":"🇰🇷","name":"korea","shortname":":flag_kr:","category":"flags","emoji_order":"2314","aliases":[":kr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kw":{"unicode":"1f1f0-1f1fc","unicode_alt":"","code_decimal":"🇰🇼","name":"kuwait","shortname":":flag_kw:","category":"flags","emoji_order":"2315","aliases":[":kw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ky":{"unicode":"1f1f0-1f1fe","unicode_alt":"","code_decimal":"🇰🇾","name":"cayman islands","shortname":":flag_ky:","category":"flags","emoji_order":"2316","aliases":[":ky:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_kz":{"unicode":"1f1f0-1f1ff","unicode_alt":"","code_decimal":"🇰🇿","name":"kazakhstan","shortname":":flag_kz:","category":"flags","emoji_order":"2317","aliases":[":kz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_la":{"unicode":"1f1f1-1f1e6","unicode_alt":"","code_decimal":"🇱🇦","name":"laos","shortname":":flag_la:","category":"flags","emoji_order":"2318","aliases":[":la:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lb":{"unicode":"1f1f1-1f1e7","unicode_alt":"","code_decimal":"🇱🇧","name":"lebanon","shortname":":flag_lb:","category":"flags","emoji_order":"2319","aliases":[":lb:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lc":{"unicode":"1f1f1-1f1e8","unicode_alt":"","code_decimal":"🇱🇨","name":"saint lucia","shortname":":flag_lc:","category":"flags","emoji_order":"2320","aliases":[":lc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_li":{"unicode":"1f1f1-1f1ee","unicode_alt":"","code_decimal":"🇱🇮","name":"liechtenstein","shortname":":flag_li:","category":"flags","emoji_order":"2321","aliases":[":li:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lk":{"unicode":"1f1f1-1f1f0","unicode_alt":"","code_decimal":"🇱🇰","name":"sri lanka","shortname":":flag_lk:","category":"flags","emoji_order":"2322","aliases":[":lk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lr":{"unicode":"1f1f1-1f1f7","unicode_alt":"","code_decimal":"🇱🇷","name":"liberia","shortname":":flag_lr:","category":"flags","emoji_order":"2323","aliases":[":lr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ls":{"unicode":"1f1f1-1f1f8","unicode_alt":"","code_decimal":"🇱🇸","name":"lesotho","shortname":":flag_ls:","category":"flags","emoji_order":"2324","aliases":[":ls:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lt":{"unicode":"1f1f1-1f1f9","unicode_alt":"","code_decimal":"🇱🇹","name":"lithuania","shortname":":flag_lt:","category":"flags","emoji_order":"2325","aliases":[":lt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lu":{"unicode":"1f1f1-1f1fa","unicode_alt":"","code_decimal":"🇱🇺","name":"luxembourg","shortname":":flag_lu:","category":"flags","emoji_order":"2326","aliases":[":lu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_lv":{"unicode":"1f1f1-1f1fb","unicode_alt":"","code_decimal":"🇱🇻","name":"latvia","shortname":":flag_lv:","category":"flags","emoji_order":"2327","aliases":[":lv:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ly":{"unicode":"1f1f1-1f1fe","unicode_alt":"","code_decimal":"🇱🇾","name":"libya","shortname":":flag_ly:","category":"flags","emoji_order":"2328","aliases":[":ly:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ma":{"unicode":"1f1f2-1f1e6","unicode_alt":"","code_decimal":"🇲🇦","name":"morocco","shortname":":flag_ma:","category":"flags","emoji_order":"2329","aliases":[":ma:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mc":{"unicode":"1f1f2-1f1e8","unicode_alt":"","code_decimal":"🇲🇨","name":"monaco","shortname":":flag_mc:","category":"flags","emoji_order":"2330","aliases":[":mc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_md":{"unicode":"1f1f2-1f1e9","unicode_alt":"","code_decimal":"🇲🇩","name":"moldova","shortname":":flag_md:","category":"flags","emoji_order":"2331","aliases":[":md:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_me":{"unicode":"1f1f2-1f1ea","unicode_alt":"","code_decimal":"🇲🇪","name":"montenegro","shortname":":flag_me:","category":"flags","emoji_order":"2332","aliases":[":me:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mf":{"unicode":"1f1f2-1f1eb","unicode_alt":"","code_decimal":"🇲🇫","name":"saint martin","shortname":":flag_mf:","category":"flags","emoji_order":"2333","aliases":[":mf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mg":{"unicode":"1f1f2-1f1ec","unicode_alt":"","code_decimal":"🇲🇬","name":"madagascar","shortname":":flag_mg:","category":"flags","emoji_order":"2334","aliases":[":mg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mh":{"unicode":"1f1f2-1f1ed","unicode_alt":"","code_decimal":"🇲🇭","name":"the marshall islands","shortname":":flag_mh:","category":"flags","emoji_order":"2335","aliases":[":mh:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mk":{"unicode":"1f1f2-1f1f0","unicode_alt":"","code_decimal":"🇲🇰","name":"macedonia","shortname":":flag_mk:","category":"flags","emoji_order":"2336","aliases":[":mk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ml":{"unicode":"1f1f2-1f1f1","unicode_alt":"","code_decimal":"🇲🇱","name":"mali","shortname":":flag_ml:","category":"flags","emoji_order":"2337","aliases":[":ml:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mm":{"unicode":"1f1f2-1f1f2","unicode_alt":"","code_decimal":"🇲🇲","name":"myanmar","shortname":":flag_mm:","category":"flags","emoji_order":"2338","aliases":[":mm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mn":{"unicode":"1f1f2-1f1f3","unicode_alt":"","code_decimal":"🇲🇳","name":"mongolia","shortname":":flag_mn:","category":"flags","emoji_order":"2339","aliases":[":mn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mo":{"unicode":"1f1f2-1f1f4","unicode_alt":"","code_decimal":"🇲🇴","name":"macau","shortname":":flag_mo:","category":"flags","emoji_order":"2340","aliases":[":mo:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mp":{"unicode":"1f1f2-1f1f5","unicode_alt":"","code_decimal":"🇲🇵","name":"northern mariana islands","shortname":":flag_mp:","category":"flags","emoji_order":"2341","aliases":[":mp:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mq":{"unicode":"1f1f2-1f1f6","unicode_alt":"","code_decimal":"🇲🇶","name":"martinique","shortname":":flag_mq:","category":"flags","emoji_order":"2342","aliases":[":mq:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mr":{"unicode":"1f1f2-1f1f7","unicode_alt":"","code_decimal":"🇲🇷","name":"mauritania","shortname":":flag_mr:","category":"flags","emoji_order":"2343","aliases":[":mr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ms":{"unicode":"1f1f2-1f1f8","unicode_alt":"","code_decimal":"🇲🇸","name":"montserrat","shortname":":flag_ms:","category":"flags","emoji_order":"2344","aliases":[":ms:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mt":{"unicode":"1f1f2-1f1f9","unicode_alt":"","code_decimal":"🇲🇹","name":"malta","shortname":":flag_mt:","category":"flags","emoji_order":"2345","aliases":[":mt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mu":{"unicode":"1f1f2-1f1fa","unicode_alt":"","code_decimal":"🇲🇺","name":"mauritius","shortname":":flag_mu:","category":"flags","emoji_order":"2346","aliases":[":mu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mv":{"unicode":"1f1f2-1f1fb","unicode_alt":"","code_decimal":"🇲🇻","name":"maldives","shortname":":flag_mv:","category":"flags","emoji_order":"2347","aliases":[":mv:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mw":{"unicode":"1f1f2-1f1fc","unicode_alt":"","code_decimal":"🇲🇼","name":"malawi","shortname":":flag_mw:","category":"flags","emoji_order":"2348","aliases":[":mw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mx":{"unicode":"1f1f2-1f1fd","unicode_alt":"","code_decimal":"🇲🇽","name":"mexico","shortname":":flag_mx:","category":"flags","emoji_order":"2349","aliases":[":mx:"],"aliases_ascii":[],"keywords":["country","mexican","flag"]},"flag_my":{"unicode":"1f1f2-1f1fe","unicode_alt":"","code_decimal":"🇲🇾","name":"malaysia","shortname":":flag_my:","category":"flags","emoji_order":"2350","aliases":[":my:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_mz":{"unicode":"1f1f2-1f1ff","unicode_alt":"","code_decimal":"🇲🇿","name":"mozambique","shortname":":flag_mz:","category":"flags","emoji_order":"2351","aliases":[":mz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_na":{"unicode":"1f1f3-1f1e6","unicode_alt":"","code_decimal":"🇳🇦","name":"namibia","shortname":":flag_na:","category":"flags","emoji_order":"2352","aliases":[":na:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_nc":{"unicode":"1f1f3-1f1e8","unicode_alt":"","code_decimal":"🇳🇨","name":"new caledonia","shortname":":flag_nc:","category":"flags","emoji_order":"2353","aliases":[":nc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ne":{"unicode":"1f1f3-1f1ea","unicode_alt":"","code_decimal":"🇳🇪","name":"niger","shortname":":flag_ne:","category":"flags","emoji_order":"2354","aliases":[":ne:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_nf":{"unicode":"1f1f3-1f1eb","unicode_alt":"","code_decimal":"🇳🇫","name":"norfolk island","shortname":":flag_nf:","category":"flags","emoji_order":"2355","aliases":[":nf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ng":{"unicode":"1f1f3-1f1ec","unicode_alt":"","code_decimal":"🇳🇬","name":"nigeria","shortname":":flag_ng:","category":"flags","emoji_order":"2356","aliases":[":nigeria:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ni":{"unicode":"1f1f3-1f1ee","unicode_alt":"","code_decimal":"🇳🇮","name":"nicaragua","shortname":":flag_ni:","category":"flags","emoji_order":"2357","aliases":[":ni:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_nl":{"unicode":"1f1f3-1f1f1","unicode_alt":"","code_decimal":"🇳🇱","name":"the netherlands","shortname":":flag_nl:","category":"flags","emoji_order":"2358","aliases":[":nl:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_no":{"unicode":"1f1f3-1f1f4","unicode_alt":"","code_decimal":"🇳🇴","name":"norway","shortname":":flag_no:","category":"flags","emoji_order":"2359","aliases":[":no:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_np":{"unicode":"1f1f3-1f1f5","unicode_alt":"","code_decimal":"🇳🇵","name":"nepal","shortname":":flag_np:","category":"flags","emoji_order":"2360","aliases":[":np:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_nr":{"unicode":"1f1f3-1f1f7","unicode_alt":"","code_decimal":"🇳🇷","name":"nauru","shortname":":flag_nr:","category":"flags","emoji_order":"2361","aliases":[":nr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_nu":{"unicode":"1f1f3-1f1fa","unicode_alt":"","code_decimal":"🇳🇺","name":"niue","shortname":":flag_nu:","category":"flags","emoji_order":"2362","aliases":[":nu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_nz":{"unicode":"1f1f3-1f1ff","unicode_alt":"","code_decimal":"🇳🇿","name":"new zealand","shortname":":flag_nz:","category":"flags","emoji_order":"2363","aliases":[":nz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_om":{"unicode":"1f1f4-1f1f2","unicode_alt":"","code_decimal":"🇴🇲","name":"oman","shortname":":flag_om:","category":"flags","emoji_order":"2364","aliases":[":om:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pa":{"unicode":"1f1f5-1f1e6","unicode_alt":"","code_decimal":"🇵🇦","name":"panama","shortname":":flag_pa:","category":"flags","emoji_order":"2365","aliases":[":pa:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pe":{"unicode":"1f1f5-1f1ea","unicode_alt":"","code_decimal":"🇵🇪","name":"peru","shortname":":flag_pe:","category":"flags","emoji_order":"2366","aliases":[":pe:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pf":{"unicode":"1f1f5-1f1eb","unicode_alt":"","code_decimal":"🇵🇫","name":"french polynesia","shortname":":flag_pf:","category":"flags","emoji_order":"2367","aliases":[":pf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pg":{"unicode":"1f1f5-1f1ec","unicode_alt":"","code_decimal":"🇵🇬","name":"papua new guinea","shortname":":flag_pg:","category":"flags","emoji_order":"2368","aliases":[":pg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ph":{"unicode":"1f1f5-1f1ed","unicode_alt":"","code_decimal":"🇵🇭","name":"the philippines","shortname":":flag_ph:","category":"flags","emoji_order":"2369","aliases":[":ph:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pk":{"unicode":"1f1f5-1f1f0","unicode_alt":"","code_decimal":"🇵🇰","name":"pakistan","shortname":":flag_pk:","category":"flags","emoji_order":"2370","aliases":[":pk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pl":{"unicode":"1f1f5-1f1f1","unicode_alt":"","code_decimal":"🇵🇱","name":"poland","shortname":":flag_pl:","category":"flags","emoji_order":"2371","aliases":[":pl:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pm":{"unicode":"1f1f5-1f1f2","unicode_alt":"","code_decimal":"🇵🇲","name":"saint pierre and miquelon","shortname":":flag_pm:","category":"flags","emoji_order":"2372","aliases":[":pm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pn":{"unicode":"1f1f5-1f1f3","unicode_alt":"","code_decimal":"🇵🇳","name":"pitcairn","shortname":":flag_pn:","category":"flags","emoji_order":"2373","aliases":[":pn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pr":{"unicode":"1f1f5-1f1f7","unicode_alt":"","code_decimal":"🇵🇷","name":"puerto rico","shortname":":flag_pr:","category":"flags","emoji_order":"2374","aliases":[":pr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ps":{"unicode":"1f1f5-1f1f8","unicode_alt":"","code_decimal":"🇵🇸","name":"palestinian authority","shortname":":flag_ps:","category":"flags","emoji_order":"2375","aliases":[":ps:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pt":{"unicode":"1f1f5-1f1f9","unicode_alt":"","code_decimal":"🇵🇹","name":"portugal","shortname":":flag_pt:","category":"flags","emoji_order":"2376","aliases":[":pt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_pw":{"unicode":"1f1f5-1f1fc","unicode_alt":"","code_decimal":"🇵🇼","name":"palau","shortname":":flag_pw:","category":"flags","emoji_order":"2377","aliases":[":pw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_py":{"unicode":"1f1f5-1f1fe","unicode_alt":"","code_decimal":"🇵🇾","name":"paraguay","shortname":":flag_py:","category":"flags","emoji_order":"2378","aliases":[":py:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_qa":{"unicode":"1f1f6-1f1e6","unicode_alt":"","code_decimal":"🇶🇦","name":"qatar","shortname":":flag_qa:","category":"flags","emoji_order":"2379","aliases":[":qa:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_re":{"unicode":"1f1f7-1f1ea","unicode_alt":"","code_decimal":"🇷🇪","name":"r\u00e9union","shortname":":flag_re:","category":"flags","emoji_order":"2380","aliases":[":re:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ro":{"unicode":"1f1f7-1f1f4","unicode_alt":"","code_decimal":"🇷🇴","name":"romania","shortname":":flag_ro:","category":"flags","emoji_order":"2381","aliases":[":ro:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_rs":{"unicode":"1f1f7-1f1f8","unicode_alt":"","code_decimal":"🇷🇸","name":"serbia","shortname":":flag_rs:","category":"flags","emoji_order":"2382","aliases":[":rs:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ru":{"unicode":"1f1f7-1f1fa","unicode_alt":"","code_decimal":"🇷🇺","name":"russia","shortname":":flag_ru:","category":"flags","emoji_order":"2383","aliases":[":ru:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_rw":{"unicode":"1f1f7-1f1fc","unicode_alt":"","code_decimal":"🇷🇼","name":"rwanda","shortname":":flag_rw:","category":"flags","emoji_order":"2384","aliases":[":rw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sa":{"unicode":"1f1f8-1f1e6","unicode_alt":"","code_decimal":"🇸🇦","name":"saudi arabia","shortname":":flag_sa:","category":"flags","emoji_order":"2385","aliases":[":saudiarabia:",":saudi:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sb":{"unicode":"1f1f8-1f1e7","unicode_alt":"","code_decimal":"🇸🇧","name":"the solomon islands","shortname":":flag_sb:","category":"flags","emoji_order":"2386","aliases":[":sb:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sc":{"unicode":"1f1f8-1f1e8","unicode_alt":"","code_decimal":"🇸🇨","name":"the seychelles","shortname":":flag_sc:","category":"flags","emoji_order":"2387","aliases":[":sc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sd":{"unicode":"1f1f8-1f1e9","unicode_alt":"","code_decimal":"🇸🇩","name":"sudan","shortname":":flag_sd:","category":"flags","emoji_order":"2388","aliases":[":sd:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_se":{"unicode":"1f1f8-1f1ea","unicode_alt":"","code_decimal":"🇸🇪","name":"sweden","shortname":":flag_se:","category":"flags","emoji_order":"2389","aliases":[":se:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sg":{"unicode":"1f1f8-1f1ec","unicode_alt":"","code_decimal":"🇸🇬","name":"singapore","shortname":":flag_sg:","category":"flags","emoji_order":"2390","aliases":[":sg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sh":{"unicode":"1f1f8-1f1ed","unicode_alt":"","code_decimal":"🇸🇭","name":"saint helena","shortname":":flag_sh:","category":"flags","emoji_order":"2391","aliases":[":sh:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_si":{"unicode":"1f1f8-1f1ee","unicode_alt":"","code_decimal":"🇸🇮","name":"slovenia","shortname":":flag_si:","category":"flags","emoji_order":"2392","aliases":[":si:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sj":{"unicode":"1f1f8-1f1ef","unicode_alt":"","code_decimal":"🇸🇯","name":"svalbard and jan mayen","shortname":":flag_sj:","category":"flags","emoji_order":"2393","aliases":[":sj:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sk":{"unicode":"1f1f8-1f1f0","unicode_alt":"","code_decimal":"🇸🇰","name":"slovakia","shortname":":flag_sk:","category":"flags","emoji_order":"2394","aliases":[":sk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sl":{"unicode":"1f1f8-1f1f1","unicode_alt":"","code_decimal":"🇸🇱","name":"sierra leone","shortname":":flag_sl:","category":"flags","emoji_order":"2395","aliases":[":sl:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sm":{"unicode":"1f1f8-1f1f2","unicode_alt":"","code_decimal":"🇸🇲","name":"san marino","shortname":":flag_sm:","category":"flags","emoji_order":"2396","aliases":[":sm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sn":{"unicode":"1f1f8-1f1f3","unicode_alt":"","code_decimal":"🇸🇳","name":"senegal","shortname":":flag_sn:","category":"flags","emoji_order":"2397","aliases":[":sn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_so":{"unicode":"1f1f8-1f1f4","unicode_alt":"","code_decimal":"🇸🇴","name":"somalia","shortname":":flag_so:","category":"flags","emoji_order":"2398","aliases":[":so:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sr":{"unicode":"1f1f8-1f1f7","unicode_alt":"","code_decimal":"🇸🇷","name":"suriname","shortname":":flag_sr:","category":"flags","emoji_order":"2399","aliases":[":sr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ss":{"unicode":"1f1f8-1f1f8","unicode_alt":"","code_decimal":"🇸🇸","name":"south sudan","shortname":":flag_ss:","category":"flags","emoji_order":"2400","aliases":[":ss:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_st":{"unicode":"1f1f8-1f1f9","unicode_alt":"","code_decimal":"🇸🇹","name":"s\u00e3o tom\u00e9 and pr\u00edncipe","shortname":":flag_st:","category":"flags","emoji_order":"2401","aliases":[":st:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sv":{"unicode":"1f1f8-1f1fb","unicode_alt":"","code_decimal":"🇸🇻","name":"el salvador","shortname":":flag_sv:","category":"flags","emoji_order":"2402","aliases":[":sv:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sx":{"unicode":"1f1f8-1f1fd","unicode_alt":"","code_decimal":"🇸🇽","name":"sint maarten","shortname":":flag_sx:","category":"flags","emoji_order":"2403","aliases":[":sx:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sy":{"unicode":"1f1f8-1f1fe","unicode_alt":"","code_decimal":"🇸🇾","name":"syria","shortname":":flag_sy:","category":"flags","emoji_order":"2404","aliases":[":sy:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_sz":{"unicode":"1f1f8-1f1ff","unicode_alt":"","code_decimal":"🇸🇿","name":"swaziland","shortname":":flag_sz:","category":"flags","emoji_order":"2405","aliases":[":sz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ta":{"unicode":"1f1f9-1f1e6","unicode_alt":"","code_decimal":"🇹🇦","name":"tristan da cunha","shortname":":flag_ta:","category":"flags","emoji_order":"2406","aliases":[":ta:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tc":{"unicode":"1f1f9-1f1e8","unicode_alt":"","code_decimal":"🇹🇨","name":"turks and caicos islands","shortname":":flag_tc:","category":"flags","emoji_order":"2407","aliases":[":tc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_td":{"unicode":"1f1f9-1f1e9","unicode_alt":"","code_decimal":"🇹🇩","name":"chad","shortname":":flag_td:","category":"flags","emoji_order":"2408","aliases":[":td:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tf":{"unicode":"1f1f9-1f1eb","unicode_alt":"","code_decimal":"🇹🇫","name":"french southern territories","shortname":":flag_tf:","category":"flags","emoji_order":"2409","aliases":[":tf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tg":{"unicode":"1f1f9-1f1ec","unicode_alt":"","code_decimal":"🇹🇬","name":"togo","shortname":":flag_tg:","category":"flags","emoji_order":"2410","aliases":[":tg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_th":{"unicode":"1f1f9-1f1ed","unicode_alt":"","code_decimal":"🇹🇭","name":"thailand","shortname":":flag_th:","category":"flags","emoji_order":"2411","aliases":[":th:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tj":{"unicode":"1f1f9-1f1ef","unicode_alt":"","code_decimal":"🇹🇯","name":"tajikistan","shortname":":flag_tj:","category":"flags","emoji_order":"2412","aliases":[":tj:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tk":{"unicode":"1f1f9-1f1f0","unicode_alt":"","code_decimal":"🇹🇰","name":"tokelau","shortname":":flag_tk:","category":"flags","emoji_order":"2413","aliases":[":tk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tl":{"unicode":"1f1f9-1f1f1","unicode_alt":"","code_decimal":"🇹🇱","name":"timor-leste","shortname":":flag_tl:","category":"flags","emoji_order":"2414","aliases":[":tl:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tm":{"unicode":"1f1f9-1f1f2","unicode_alt":"","code_decimal":"🇹🇲","name":"turkmenistan","shortname":":flag_tm:","category":"flags","emoji_order":"2415","aliases":[":turkmenistan:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tn":{"unicode":"1f1f9-1f1f3","unicode_alt":"","code_decimal":"🇹🇳","name":"tunisia","shortname":":flag_tn:","category":"flags","emoji_order":"2416","aliases":[":tn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_to":{"unicode":"1f1f9-1f1f4","unicode_alt":"","code_decimal":"🇹🇴","name":"tonga","shortname":":flag_to:","category":"flags","emoji_order":"2417","aliases":[":to:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tr":{"unicode":"1f1f9-1f1f7","unicode_alt":"","code_decimal":"🇹🇷","name":"turkey","shortname":":flag_tr:","category":"flags","emoji_order":"2418","aliases":[":tr:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tt":{"unicode":"1f1f9-1f1f9","unicode_alt":"","code_decimal":"🇹🇹","name":"trinidad and tobago","shortname":":flag_tt:","category":"flags","emoji_order":"2419","aliases":[":tt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tv":{"unicode":"1f1f9-1f1fb","unicode_alt":"","code_decimal":"🇹🇻","name":"tuvalu","shortname":":flag_tv:","category":"flags","emoji_order":"2420","aliases":[":tuvalu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tw":{"unicode":"1f1f9-1f1fc","unicode_alt":"","code_decimal":"🇹🇼","name":"the republic of china","shortname":":flag_tw:","category":"flags","emoji_order":"2421","aliases":[":tw:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_tz":{"unicode":"1f1f9-1f1ff","unicode_alt":"","code_decimal":"🇹🇿","name":"tanzania","shortname":":flag_tz:","category":"flags","emoji_order":"2422","aliases":[":tz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ua":{"unicode":"1f1fa-1f1e6","unicode_alt":"","code_decimal":"🇺🇦","name":"ukraine","shortname":":flag_ua:","category":"flags","emoji_order":"2423","aliases":[":ua:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ug":{"unicode":"1f1fa-1f1ec","unicode_alt":"","code_decimal":"🇺🇬","name":"uganda","shortname":":flag_ug:","category":"flags","emoji_order":"2424","aliases":[":ug:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_um":{"unicode":"1f1fa-1f1f2","unicode_alt":"","code_decimal":"🇺🇲","name":"united states minor outlying islands","shortname":":flag_um:","category":"flags","emoji_order":"2425","aliases":[":um:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_us":{"unicode":"1f1fa-1f1f8","unicode_alt":"","code_decimal":"🇺🇸","name":"united states","shortname":":flag_us:","category":"flags","emoji_order":"2427","aliases":[":us:"],"aliases_ascii":[],"keywords":["america","country","flag"]},"flag_uy":{"unicode":"1f1fa-1f1fe","unicode_alt":"","code_decimal":"🇺🇾","name":"uruguay","shortname":":flag_uy:","category":"flags","emoji_order":"2428","aliases":[":uy:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_uz":{"unicode":"1f1fa-1f1ff","unicode_alt":"","code_decimal":"🇺🇿","name":"uzbekistan","shortname":":flag_uz:","category":"flags","emoji_order":"2429","aliases":[":uz:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_va":{"unicode":"1f1fb-1f1e6","unicode_alt":"","code_decimal":"🇻🇦","name":"the vatican city","shortname":":flag_va:","category":"flags","emoji_order":"2430","aliases":[":va:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_vc":{"unicode":"1f1fb-1f1e8","unicode_alt":"","code_decimal":"🇻🇨","name":"saint vincent and the grenadines","shortname":":flag_vc:","category":"flags","emoji_order":"2431","aliases":[":vc:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ve":{"unicode":"1f1fb-1f1ea","unicode_alt":"","code_decimal":"🇻🇪","name":"venezuela","shortname":":flag_ve:","category":"flags","emoji_order":"2432","aliases":[":ve:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_vg":{"unicode":"1f1fb-1f1ec","unicode_alt":"","code_decimal":"🇻🇬","name":"british virgin islands","shortname":":flag_vg:","category":"flags","emoji_order":"2433","aliases":[":vg:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_vi":{"unicode":"1f1fb-1f1ee","unicode_alt":"","code_decimal":"🇻🇮","name":"u.s. virgin islands","shortname":":flag_vi:","category":"flags","emoji_order":"2434","aliases":[":vi:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_vn":{"unicode":"1f1fb-1f1f3","unicode_alt":"","code_decimal":"🇻🇳","name":"vietnam","shortname":":flag_vn:","category":"flags","emoji_order":"2435","aliases":[":vn:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_vu":{"unicode":"1f1fb-1f1fa","unicode_alt":"","code_decimal":"🇻🇺","name":"vanuatu","shortname":":flag_vu:","category":"flags","emoji_order":"2436","aliases":[":vu:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_wf":{"unicode":"1f1fc-1f1eb","unicode_alt":"","code_decimal":"🇼🇫","name":"wallis and futuna","shortname":":flag_wf:","category":"flags","emoji_order":"2437","aliases":[":wf:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ws":{"unicode":"1f1fc-1f1f8","unicode_alt":"","code_decimal":"🇼🇸","name":"samoa","shortname":":flag_ws:","category":"flags","emoji_order":"2438","aliases":[":ws:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_xk":{"unicode":"1f1fd-1f1f0","unicode_alt":"","code_decimal":"🇽🇰","name":"kosovo","shortname":":flag_xk:","category":"flags","emoji_order":"2439","aliases":[":xk:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_ye":{"unicode":"1f1fe-1f1ea","unicode_alt":"","code_decimal":"🇾🇪","name":"yemen","shortname":":flag_ye:","category":"flags","emoji_order":"2440","aliases":[":ye:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_yt":{"unicode":"1f1fe-1f1f9","unicode_alt":"","code_decimal":"🇾🇹","name":"mayotte","shortname":":flag_yt:","category":"flags","emoji_order":"2441","aliases":[":yt:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_za":{"unicode":"1f1ff-1f1e6","unicode_alt":"","code_decimal":"🇿🇦","name":"south africa","shortname":":flag_za:","category":"flags","emoji_order":"2442","aliases":[":za:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_zm":{"unicode":"1f1ff-1f1f2","unicode_alt":"","code_decimal":"🇿🇲","name":"zambia","shortname":":flag_zm:","category":"flags","emoji_order":"2443","aliases":[":zm:"],"aliases_ascii":[],"keywords":["country","flag"]},"flag_zw":{"unicode":"1f1ff-1f1fc","unicode_alt":"","code_decimal":"🇿🇼","name":"zimbabwe","shortname":":flag_zw:","category":"flags","emoji_order":"2444","aliases":[":zw:"],"aliases_ascii":[],"keywords":["country","flag"]},"regional_indicator_z":{"unicode":"1f1ff","unicode_alt":"","code_decimal":"🇿","name":"regional indicator symbol letter z","shortname":":regional_indicator_z:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_y":{"unicode":"1f1fe","unicode_alt":"","code_decimal":"🇾","name":"regional indicator symbol letter y","shortname":":regional_indicator_y:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_x":{"unicode":"1f1fd","unicode_alt":"","code_decimal":"🇽","name":"regional indicator symbol letter x","shortname":":regional_indicator_x:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_w":{"unicode":"1f1fc","unicode_alt":"","code_decimal":"🇼","name":"regional indicator symbol letter w","shortname":":regional_indicator_w:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_v":{"unicode":"1f1fb","unicode_alt":"","code_decimal":"🇻","name":"regional indicator symbol letter v","shortname":":regional_indicator_v:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_u":{"unicode":"1f1fa","unicode_alt":"","code_decimal":"🇺","name":"regional indicator symbol letter u","shortname":":regional_indicator_u:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_t":{"unicode":"1f1f9","unicode_alt":"","code_decimal":"🇹","name":"regional indicator symbol letter t","shortname":":regional_indicator_t:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_s":{"unicode":"1f1f8","unicode_alt":"","code_decimal":"🇸","name":"regional indicator symbol letter s","shortname":":regional_indicator_s:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_r":{"unicode":"1f1f7","unicode_alt":"","code_decimal":"🇷","name":"regional indicator symbol letter r","shortname":":regional_indicator_r:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_q":{"unicode":"1f1f6","unicode_alt":"","code_decimal":"🇶","name":"regional indicator symbol letter q","shortname":":regional_indicator_q:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_p":{"unicode":"1f1f5","unicode_alt":"","code_decimal":"🇵","name":"regional indicator symbol letter p","shortname":":regional_indicator_p:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_o":{"unicode":"1f1f4","unicode_alt":"","code_decimal":"🇴","name":"regional indicator symbol letter o","shortname":":regional_indicator_o:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_n":{"unicode":"1f1f3","unicode_alt":"","code_decimal":"🇳","name":"regional indicator symbol letter n","shortname":":regional_indicator_n:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_m":{"unicode":"1f1f2","unicode_alt":"","code_decimal":"🇲","name":"regional indicator symbol letter m","shortname":":regional_indicator_m:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_l":{"unicode":"1f1f1","unicode_alt":"","code_decimal":"🇱","name":"regional indicator symbol letter l","shortname":":regional_indicator_l:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_k":{"unicode":"1f1f0","unicode_alt":"","code_decimal":"🇰","name":"regional indicator symbol letter k","shortname":":regional_indicator_k:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_j":{"unicode":"1f1ef","unicode_alt":"","code_decimal":"🇯","name":"regional indicator symbol letter j","shortname":":regional_indicator_j:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_i":{"unicode":"1f1ee","unicode_alt":"","code_decimal":"🇮","name":"regional indicator symbol letter i","shortname":":regional_indicator_i:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_h":{"unicode":"1f1ed","unicode_alt":"","code_decimal":"🇭","name":"regional indicator symbol letter h","shortname":":regional_indicator_h:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_g":{"unicode":"1f1ec","unicode_alt":"","code_decimal":"🇬","name":"regional indicator symbol letter g","shortname":":regional_indicator_g:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_f":{"unicode":"1f1eb","unicode_alt":"","code_decimal":"🇫","name":"regional indicator symbol letter f","shortname":":regional_indicator_f:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_e":{"unicode":"1f1ea","unicode_alt":"","code_decimal":"🇪","name":"regional indicator symbol letter e","shortname":":regional_indicator_e:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_d":{"unicode":"1f1e9","unicode_alt":"","code_decimal":"🇩","name":"regional indicator symbol letter d","shortname":":regional_indicator_d:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_c":{"unicode":"1f1e8","unicode_alt":"","code_decimal":"🇨","name":"regional indicator symbol letter c","shortname":":regional_indicator_c:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_b":{"unicode":"1f1e7","unicode_alt":"","code_decimal":"🇧","name":"regional indicator symbol letter b","shortname":":regional_indicator_b:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]},"regional_indicator_a":{"unicode":"1f1e6","unicode_alt":"","code_decimal":"🇦","name":"regional indicator symbol letter a","shortname":":regional_indicator_a:","category":"regional","emoji_order":"12345","aliases":[],"aliases_ascii":[],"keywords":[]}} diff --git a/resources/fonts/EmojiOne/emojione-android.ttf b/resources/fonts/EmojiOne/emojione-android.ttf deleted file mode 100644 index 4cd640d0..00000000 Binary files a/resources/fonts/EmojiOne/emojione-android.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/LICENSE.txt b/resources/fonts/OpenSans/LICENSE.txt deleted file mode 100644 index d6456956..00000000 --- a/resources/fonts/OpenSans/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/resources/fonts/OpenSans/OpenSans-Bold.ttf b/resources/fonts/OpenSans/OpenSans-Bold.ttf deleted file mode 100644 index fd79d43b..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-Bold.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-BoldItalic.ttf b/resources/fonts/OpenSans/OpenSans-BoldItalic.ttf deleted file mode 100644 index 9bc80095..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-BoldItalic.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-ExtraBold.ttf b/resources/fonts/OpenSans/OpenSans-ExtraBold.ttf deleted file mode 100644 index 21f6f84a..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-ExtraBold.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf b/resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf deleted file mode 100644 index 31cb6883..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-Italic.ttf b/resources/fonts/OpenSans/OpenSans-Italic.ttf deleted file mode 100644 index c90da48f..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-Italic.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-Light.ttf b/resources/fonts/OpenSans/OpenSans-Light.ttf deleted file mode 100644 index 0d381897..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-Light.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-LightItalic.ttf b/resources/fonts/OpenSans/OpenSans-LightItalic.ttf deleted file mode 100644 index 68299c4b..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-LightItalic.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-Regular.ttf b/resources/fonts/OpenSans/OpenSans-Regular.ttf deleted file mode 100644 index db433349..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-Regular.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-Semibold.ttf b/resources/fonts/OpenSans/OpenSans-Semibold.ttf deleted file mode 100644 index 1a7679e3..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-Semibold.ttf and /dev/null differ diff --git a/resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf b/resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf deleted file mode 100644 index 59b6d16b..00000000 Binary files a/resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf and /dev/null differ diff --git a/resources/icons/ui/at-solid.svg b/resources/icons/ui/at-solid.svg new file mode 100644 index 00000000..8b72d6f8 --- /dev/null +++ b/resources/icons/ui/at-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/ui/mail-reply.png b/resources/icons/ui/mail-reply.png new file mode 100644 index 00000000..a9d377d0 Binary files /dev/null and b/resources/icons/ui/mail-reply.png differ diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index 81e440fe..63772bf9 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -2,42 +2,155 @@ - AudioItem + ChatPage - - Save File - Datei speichern + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Failed to invite %1 to %2: %3 + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + Medienupload fehlgeschlagen. Bitte versuche es erneut. + + + + Failed to restore OLM account. Please login again. + Wiederherstellung des OLM Accounts fehlgeschlagen. Bitte logge dich erneut ein. + + + + Failed to restore save data. Please login again. + Gespeicherte Nachrichten konnten nicht wiederhergestellt werden. Bitte melde Dich erneut an. + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + Fehler beim Setup der Verschlüsselungsschlüssel. Servermeldung: %1 %2. Bitte versuche es später erneut. + + + + + Please try to login again: %1 + Bitte melde dich erneut an: %1 + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + Raum konnte nicht erstellt werden: %1 + + + + Room %1 created + + + + + Failed to leave room: %1 + Konnte den Raum nicht verlassen: %1 - DateSeparator + CommunitiesListItem - - Today - Heute + + All rooms + Alle Räume + + + + Favourite rooms + Favoriten - Yesterday - Gestern + Low priority rooms + Räume niedriger Priorität + + + + + (tag) + (tag) + + + + (community) + (community) EditModal - - APPLY - EINSETZEN + + Apply + Anwenden - - CANCEL - ABBRECHEN + + Cancel + Abbrechen - + Name - Titel + Name @@ -46,25 +159,25 @@ - FileItem + EncryptionIndicator - - Save File - Datei speichern + + Encrypted + Verschlüsselt - ImageItem + InviteeItem - - Save image - Bild speichern + + Remove + Löschen LoginPage - + Matrix ID Matrix-ID @@ -78,65 +191,113 @@ Password Passwort + + + Device name + Gerätename + LOGIN ANMELDEN - + + Autodiscovery failed. Received malformed response. + Automatische Erkennung fehlgeschlagen. Antwort war fehlerhaft. + + + + Autodiscovery failed. Unknown error when requesting .well-known. + Automatische Erkennung fehlgeschlagen. Unbekannter Fehler bei Anfrage .well-known. + + + + The required endpoints were not found. Possibly not a Matrix server. + Benötigte Ansprechpunkte nicht auffindbar. Möglicherweise kein Matrixserver. + + + + Received malformed response. Make sure the homeserver domain is valid. + Erhaltene Antwort war fehlerhaft. Bitte Homeserverdomain prüfen. + + + + An unknown error occured. Make sure the homeserver domain is valid. + Ein unbekannter Fehler ist aufgetreten. Bitte Homeserverdomain prüfen. + + + Empty password Leeres Passwort - - MatrixClient - - - Wrong username or password - Falscher Benutzername oder Passwort - - - - Login endpoint was not found on the server - Login-Endpunkt wurde auf dem Server nicht gefunden - - - - An unknown error occured. Please try again. - Ein unbekannter Fehler trat auf. Bitte erneut versuchen. - - - - Malformed response. Possibly not a Matrix server - Ungewöhnliche Antwort. Vielleicht kein Matrix-Server - - MemberList - + Room members Teilnehmerliste - - SHOW MORE - MEHR ZEIGEN + + OK + OK + + + + MessageDelegate + + + redacted + gelöscht + + + + Encryption enabled + Verschlüsselung aktiviert + + + + room name changed to: %1 + Raumname wurde gändert auf: %1 + + + + removed room name + Raumname wurde entfernt + + + + topic changed to: %1 + Raumthema wurde geändert auf: %1 + + + + removed topic + Raumthema wurde entfernt. + + + + Placeholder + + + unimplemented event: + Unimplementiertes Event: QuickSwitcher - + Search for a room... - Raum suchen... + Raum suchen… RegisterPage - + Username Benutzername @@ -153,15 +314,15 @@ Home Server - Heimserver + Homeserver - + REGISTER REGISTRIEREN - + Invalid username Ungültiger Benutzername @@ -178,23 +339,39 @@ Invalid server name - Ungültiger Server-Name + Ungültiger Servername + + + + ReplyPopup + + + Logout + Abmelden + + + + RoomInfo + + + no version stored + keine Version gespeichert RoomInfoListItem - + Leave room Raum verlassen - + Accept - Akzeptieren + Akzeptieren - + Decline Ablehnen @@ -202,7 +379,12 @@ SideBarActions - + + User settings + Benutzereinstellungen + + + Create new room Neuen Raum erstellen @@ -211,16 +393,65 @@ Join a room Raum betreten + + + Start a new chat + Neues Gespräch beginnen + + + + Room directory + Raumverzeichnis + + + + StatusIndicator + + + Failed + Fehlgeschlagen + + + + Sent + Gesendet + + + + Received + Empfangen + + + + Read + Gelesen + TextInputWidget - - Write a message... - Schreibe eine Nachricht... + + Send a file + Versende Datei - + + + Write a message... + Schreibe eine Nachricht… + + + + Send a message + Versende eine Nachricht + + + + Emoji + Emoji + + + Select a file Datei auswählen @@ -229,11 +460,220 @@ All Files (*) Alle Dateien (*) + + + Connection lost. Nheko is trying to re-connect... + Verbindung verloren. Nheko versucht sie wieder aufzunehmen… + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + -- verschlüsselter Event (keine Schlüssel zur Entschlüsselung gefunden) -- + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + -- Entschlüsselungsfehler (Fehler bei Kommunikation mit Datenbank) -- + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + -- Entschlüsselungsfehler (Fehler bei Suche nach megolm Schlüsseln in Datenbank) -- + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + -- Entschlüsselungsfehler (%1) -- + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + -- verschlüsselter Event (Unbekannter Eventtyp) -- + + + + Message redaction failed: %1 + Nachricht zurückziehen fehlgeschlagen: %1 + + + + Save image + Bild speichern + + + + Save video + Video speichern + + + + Save audio + Audiodatei speichern + + + + Save file + Datei speichern + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + %1%2 tippt + %1 und %2 tippen + + + + + %1 was invited. + %1 wurde eingeladen. + + + + %1 changed their display name and avatar. + %1 hat den Anzeigenamen und Avatar geändert. + + + + %1 changed their display name. + %1 hat den Anzeigenamen geändert. + + + + %1 changed their avatar. + %1 hat den Avatar geändert. + + + + %1 joined. + %1 hat den Raum betreten. + + + + %1 rejected their invite. + %1 hat die Einladung abgewiesen. + + + + Revoked the invite to %1. + Hat die Einladung an %1 zurückgezogen. + + + + %1 left the room. + %1 hat den Raum verlassen. + + + + Kicked %1. + %1 wurde gekickt. + + + + Unbanned %1 + Hat die Verbannung von %1 zurückgezogen. + + + + %1 redacted their knock. + %1 hat das Anklopfen zurückgezogen. + + + + Rejected the knock from %1. + Hat das Anklopfen von %1 abgewiesen. + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + %1 hat den Raum verlassen. + + + + %1 was banned. + %1 wurde gebannt. + + + + %1 knocked. + %1 hat angeklopft. + + + + TimelineRow + + + Reply + Antworten + + + + Options + Optionen + + + + TimelineView + + + Read receipts + Lesebestätigungen + + + + Mark as read + Als gelesen markieren + + + + View raw message + Zeige rohen Nachrichteninhalt + + + + Redact message + Nachricht löschen + + + + Save as + Speichern als... + + + + No room open + Kein Raum geöffnet + + + + Close + Schließen + TopRoomBar - + + Room options + Raumoptionen + + + + Mentions + Erwähnungen + + + Invite users Benutzer einladen @@ -256,7 +696,7 @@ TrayIcon - + Show Zeigen @@ -267,100 +707,209 @@ - TypingDisplay + UserInfoWidget - - is typing - tippt - - - - are typing - tippen + + Logout + Abmelden UserSettingsPage - - User Settings - Benutzereinstellungen - - - + Minimize to tray Ins Benachrichtigungsfeld minimieren - + Start in tray Im Benachrichtigungsfeld starten - - Re-order rooms based on activity - Räume nach Aktivität sortieren - - - + Group's sidebar Gruppen-Seitenleiste - + + Circular Avatars + Runde Profilbilder + + + Typing notifications Schreibbenachrichtigungen - + Read receipts Lesebestätigungen - + + Send messages as Markdown + + + + + Desktop notifications + Desktopbenachrichtigungen + + + + Scale factor + Skalierungsfaktor + + + + Font size + Schriftgröße + + + + Font Family + Schriftart + + + + Emoji Font Famly + Emoji Schriftart + + + Theme Erscheinungsbild - + + Device ID + Geräte-ID + + + + Device Fingerprint + Gerätefingerabdruck + + + + Session Keys + Sitzungsschlüssel + + + + IMPORT + IMPORTIEREN + + + + EXPORT + EXPORTIEREN + + + + ENCRYPTION + VERSCHLÜSSELUNG + + + GENERAL ALLGEMEINES + + + Open Sessions File + Öffne Sessions Datei + + + + + + + + + + + + + Error + Feher + + + + + File Password + Password für Datei + + + + Enter the passphrase to decrypt the file: + Bitte gib das Passwort zum Enschlüsseln der Datei ein: + + + + + The password cannot be empty + Das Passwort darf nicht leer sein + + + + Enter passphrase to encrypt your session keys: + Bitte gib das Passwort zum Verschlüsseln der Sitzungsschlüssel ein: + + + + File to save the exported session keys + Datei zum Speichern der zu exportierenden Sitzungsschlüssel + WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. - Willkommen bei nheko, dem Desktop-Client für das Matrix-Protokoll. + Willkommen bei nheko! Ein Desktop-Client für das Matrix-Protokoll. Enjoy your stay! - Genieße deinen Aufenthalt! + Viel Vergnügen! - + REGISTER REGISTRIEREN - + LOGIN ANMELDEN + + descriptiveTime + + + Yesterday + Gestern + + dialogs::CreateRoom - - CANCEL - ABBRECHEN + + Create room + Raum erstellen - + + Cancel + Abbrechen + + + Name - Titel + Raumname @@ -370,7 +919,7 @@ Alias - Alias + Raumalias @@ -378,12 +927,12 @@ Raumsichtbarkeit - + Room Preset Raumvorlage - + Direct Chat Direkter Chat @@ -391,12 +940,12 @@ dialogs::InviteUsers - - CANCEL - ABBRECHEN + + Cancel + Abbrechen - + User ID to invite Benutzer-ID, die eingeladen werden soll @@ -404,12 +953,17 @@ dialogs::JoinRoom - - CANCEL - ABBRECHEN + + Join + Betreten - + + Cancel + Abbrechen + + + Room ID or alias Raum-ID oder -Alias @@ -417,12 +971,12 @@ dialogs::LeaveRoom - - CANCEL - ABBRECHEN + + Cancel + Abbrechen - + Are you sure you want to leave? Willst du wirklich den Raum verlassen? @@ -430,12 +984,12 @@ dialogs::Logout - - CANCEL - ABBRECHEN + + Cancel + Abbrechen - + Logout. Are you sure? Willst du dich wirklich abmelden? @@ -443,7 +997,7 @@ dialogs::PreviewUploadOverlay - + Upload Hochladen @@ -453,7 +1007,7 @@ Abbrechen - + Media type: %1 Media size: %2 @@ -465,14 +1019,14 @@ Medien-Größe: %2 dialogs::ReCaptcha - - CONFIRM - BESTÄTIGEN + + Cancel + Abbrechen - - CANCEL - ABBRECHEN + + Confirm + Bestätigen @@ -483,30 +1037,63 @@ Medien-Größe: %2 dialogs::ReadReceipts - + Read receipts Lesebestätigungen + + + Close + Schließen + + + + dialogs::ReceiptItem + + + Today %1 + Heute %1 + + + + Yesterday %1 + Gestern %1 + dialogs::RoomSettings - - CANCEL - ABBRECHEN + + Settings + Einstellungen - + + Info + Informationen + + + + Internal ID + interne ID + + + + Room Version + Raumversion + + + Notifications Benachrichtigungen - + Muted Stumm - + Mentions only Nur Erwähnungen @@ -516,7 +1103,7 @@ Medien-Größe: %2 Alle Nachrichten - + Room access Raumzugang @@ -535,11 +1122,105 @@ Medien-Größe: %2 Invited users Nur Eingeladene + + + Encryption + Verschlüsselung + + + + End-to-End Encryption + Ende-zu-Ende Verschlüsselung + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + Verschlüsselung befindet sich momentan in einem experimentellen Stadium, unerwartete Fehler können auftreten. <br>Sie kann anschließend nicht wieder deaktiviert werden. + + + + Respond to key requests + Schlüsselnfrage beantworten + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + Ob nheko automatisch auf Anfragen mit Sessionschlüsseln antworten soll, oder nicht. Bitte mit Vorsicht nutzen, da dies eine temporäre Massnahme ist. Sie dient dem Test von E2E Verschlüsselung, bis die Geräteverifikation fertig gestellt ist. + + + + %n member(s) + + %n Teilnehmer + %n Teilnehmer + + + + + Failed to enable encryption: %1 + Aktivierung der Verschlüsselung fehlgeschlagen: %1 + + + + Select an avatar + Wähle einen Avatar + + + + All Files (*) + Alle Dateien (*) + + + + The selected file is not an image + Die ausgewählte Datei ist kein Bild + + + + Error while reading file: %1 + Fehler beim Lesen der DateI: %1 + + + + + Failed to upload image: %s + Hochladen der Bilddatei fehlgeschlagen: %s + + + + dialogs::UserProfile + + + Ban the user from the room + Banne den Nutzer aus diesem Raum + + + + Ignore messages from this user + Nachrichten von diesem Nutzer ignorieren + + + + Kick the user from the room + Entferne diesen Nutzer aus dem Raum + + + + Start a conversation + Gespräch beginnen + + + + Devices + Geräte + emoji::Panel - + Smileys & People Smileys & Personen @@ -579,4 +1260,108 @@ Medien-Größe: %2 Flaggen + + message-description sent: + + + You sent an audio clip + Du hast eine Audiodatei gesendet. + + + + %1 sent an audio clip + %1 hat eine Audiodatei gesendet. + + + + You sent an image + Du hast ein Bild gesendet. + + + + %1 sent an image + %1 hat ein Bild gesendet. + + + + You sent a file + Du hast eine Datei gesendet. + + + + %1 sent a file + %1 hat eine Datei gesendet. + + + + You sent a video + Du hast ein Video gesendet. + + + + %1 sent a video + %1 hat ein Video gesendet. + + + + You sent a sticker + Du hast einen Sticker gesendet. + + + + %1 sent a sticker + %1 hat einen Sticker gesendet. + + + + You sent a notification + Du hast eine Benachrichtigung gesendet. + + + + %1 sent a notification + %1 hat eine Benachrichtigung gesendet. + + + + You: %1 + Du: %1 + + + + %1: %2 + %1: %2 + + + + You sent an encrypted message + Du hast eine verschlüsselte Nachricht gesendet. + + + + %1 sent an encrypted message + %1 hat eine verschlüsselte Nachricht gesendet. + + + + popups::UserMentions + + + This Room + Dieser Raum + + + + All Rooms + Alle Räume + + + + utils + + + Unknown Message Type + Unbekannter Nachrichtentyp + + diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index 1f993fea..39471c09 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -2,40 +2,153 @@ - AudioItem + ChatPage - - Save File - Αποθήκευση + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Failed to invite %1 to %2: %3 + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + + Failed to restore OLM account. Please login again. + + + + + Failed to restore save data. Please login again. + + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + + Please try to login again: %1 + + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + + + + + Room %1 created + + + + + Failed to leave room: %1 + - DateSeparator + CommunitiesListItem - - Today - Σήμερα + + All rooms + + + + + Favourite rooms + - Yesterday - Χθές + Low priority rooms + + + + + + (tag) + + + + + (community) + EditModal - - APPLY + + Apply - - CANCEL - ΑΚΥΡΟ + + Cancel + Άκυρο - + Name Όνομα @@ -46,25 +159,25 @@ - FileItem + EncryptionIndicator - - Save File - Αποθήκευση + + Encrypted + - ImageItem + InviteeItem - - Save image - Αποθήκευση Εικόνας + + Remove + LoginPage - + Matrix ID Matrix ID @@ -78,57 +191,105 @@ Password Κωδικός + + + Device name + + LOGIN ΕΙΣΟΔΟΣ - + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + + The required endpoints were not found. Possibly not a Matrix server. + + + + + Received malformed response. Make sure the homeserver domain is valid. + + + + + An unknown error occured. Make sure the homeserver domain is valid. + + + + Empty password Κενός κωδικός - - MatrixClient - - - Wrong username or password - Λανθασμένο όνμα χρήστη ή κωδικός - - - - Login endpoint was not found on the server - - - - - An unknown error occured. Please try again. - - - - - Malformed response. Possibly not a Matrix server - - - MemberList - + Room members Μέλη - - SHOW MORE - ΠΕΡΙΣΣΟΤΕΡΑ + + OK + + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + QuickSwitcher - + Search for a room... Αναζήτηση συνομιλίας... @@ -136,7 +297,7 @@ RegisterPage - + Username Όνομα χρήστη @@ -156,12 +317,12 @@ Διακομιστής - + REGISTER ΕΓΓΡΑΦΗ - + Invalid username Μη έγκυρο όνομα χρήστη @@ -181,20 +342,36 @@ Λανθασμένο όνομα διακομιστή + + ReplyPopup + + + Logout + + + + + RoomInfo + + + no version stored + + + RoomInfoListItem - + Leave room Βγές - + Accept Αποδοχή - + Decline Απόρριψη @@ -202,7 +379,12 @@ SideBarActions - + + User settings + + + + Create new room Νέα συνομιλία @@ -211,16 +393,65 @@ Join a room + + + Start a new chat + + + + + Room directory + + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read + + TextInputWidget - + + Send a file + + + + + Write a message... Γράψε ένα μήνυμα... - + + Send a message + + + + + Emoji + + + + Select a file Διάλεξε ένα αρχείο @@ -229,11 +460,220 @@ All Files (*) Όλα τα αρχεία (*) + + + Connection lost. Nheko is trying to re-connect... + + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + + + + + Message redaction failed: %1 + + + + + Save image + Αποθήκευση Εικόνας + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + + + + + TimelineView + + + Read receipts + + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + + TopRoomBar - + + Room options + + + + + Mentions + + + + Invite users Προσκάλεσε χρήστες @@ -256,7 +696,7 @@ TrayIcon - + Show Εμφάνιση @@ -267,70 +707,166 @@ - TypingDisplay + UserInfoWidget - - is typing - πληκτρολογεί - - - - are typing - πληκτρολογούν + + Logout + UserSettingsPage - - User Settings - Ρυθμίσεις Χρήστη - - - + Minimize to tray Ελαχιστοποίηση - + Start in tray - - Re-order rooms based on activity - - - - + Group's sidebar - + + Circular Avatars + + + + Typing notifications - + Read receipts - + + Send messages as Markdown + + + + + Desktop notifications + + + + + Scale factor + + + + + Font size + + + + + Font Family + + + + + Emoji Font Famly + + + + Theme Φόντο - + + Device ID + + + + + Device Fingerprint + + + + + Session Keys + + + + + IMPORT + + + + + EXPORT + + + + + ENCRYPTION + + + + GENERAL ΓΕΝΙΚΑ + + + Open Sessions File + + + + + + + + + + + + + + Error + + + + + + File Password + + + + + Enter the passphrase to decrypt the file: + + + + + + The password cannot be empty + + + + + Enter passphrase to encrypt your session keys: + + + + + File to save the exported session keys + + WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. Καλως ήρθες στο nheko! @@ -340,25 +876,38 @@ - + REGISTER ΕΓΓΡΑΦΗ - + LOGIN ΕΙΣΟΔΟΣ + + descriptiveTime + + + Yesterday + + + dialogs::CreateRoom - - CANCEL - ΑΚΥΡΟ + + Create room + - + + Cancel + Άκυρο + + + Name Όνομα @@ -378,12 +927,12 @@ - + Room Preset - + Direct Chat Άμεση συνομιλία @@ -391,12 +940,12 @@ dialogs::InviteUsers - - CANCEL - ΑΚΥΡΟ + + Cancel + Άκυρο - + User ID to invite Όνομα χρήστη @@ -404,12 +953,17 @@ dialogs::JoinRoom - - CANCEL - ΑΚΥΡΟ + + Join + - + + Cancel + Άκυρο + + + Room ID or alias ID ή όνομα συνομιλίας @@ -417,12 +971,12 @@ dialogs::LeaveRoom - - CANCEL - ΑΚΥΡΟ + + Cancel + Άκυρο - + Are you sure you want to leave? Είστε σίγουροι οτι θέλετε να κλείσετε τη συνομιλία; @@ -430,12 +984,12 @@ dialogs::Logout - - CANCEL - ΑΚΥΡΟ + + Cancel + Άκυρο - + Logout. Are you sure? Αποσύνδεση. Είστε σίγουροι; @@ -443,7 +997,7 @@ dialogs::PreviewUploadOverlay - + Upload Μεταφόρτωση @@ -453,7 +1007,7 @@ Άκυρο - + Media type: %1 Media size: %2 @@ -463,14 +1017,14 @@ Media size: %2 dialogs::ReCaptcha - - CONFIRM - ΕΠΙΒΕΒΑΙΩΣΗ + + Cancel + Άκυρο - - CANCEL - ΑΚΥΡΟ + + Confirm + @@ -481,30 +1035,63 @@ Media size: %2 dialogs::ReadReceipts - + Read receipts + + + Close + + + + + dialogs::ReceiptItem + + + Today %1 + + + + + Yesterday %1 + + dialogs::RoomSettings - - CANCEL - ΑΚΥΡΟ + + Settings + Ρυθμίσεις - + + Info + + + + + Internal ID + + + + + Room Version + + + + Notifications Ειδοποιήσεις - + Muted Αθόρυβο - + Mentions only Αναφορές μόνο @@ -514,7 +1101,7 @@ Media size: %2 - + Room access @@ -533,11 +1120,105 @@ Media size: %2 Invited users Μόνο με πρόσκληση + + + Encryption + + + + + End-to-End Encryption + + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + + + + + Respond to key requests + + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + + + + + %n member(s) + + + + + + + + Failed to enable encryption: %1 + + + + + Select an avatar + + + + + All Files (*) + Όλα τα αρχεία (*) + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + + Failed to upload image: %s + + + + + dialogs::UserProfile + + + Ban the user from the room + + + + + Ignore messages from this user + + + + + Kick the user from the room + + + + + Start a conversation + + + + + Devices + + emoji::Panel - + Smileys & People Πρόσωπα @@ -577,4 +1258,108 @@ Media size: %2 Σημαίες + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 4f4db00d..ba462b41 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -2,579 +2,1368 @@ - AudioItem + ChatPage - - Save File + + Failed to invite user: %1 + + + + Invited user: %1 + + + + + Failed to invite %1 to %2: %3 + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + + Failed to restore OLM account. Please login again. + Failed to restore OLM account. Please login again. + + + + Failed to restore save data. Please login again. + Failed to restore save data. Please login again. + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + Please try to login again: %1 + Please try to login again: %1 + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + Room creation failed: %1 + + + + Room %1 created + + + + + Failed to leave room: %1 + Failed to leave room: %1 + - DateSeparator + CommunitiesListItem - - Today - + + All rooms + All rooms + + + + Favourite rooms + Favourite rooms - Yesterday - + Low priority rooms + Low priority rooms + + + + + (tag) + (tag) + + + + (community) + (community) EditModal - - APPLY - + + Apply + Apply - - CANCEL - + + Cancel + Cancel - + Name - + Name Topic + Topic + + + + EncryptionIndicator + + + Encrypted - FileItem + InviteeItem - - Save File - - - - - ImageItem - - - Save image - + + Remove + Remove LoginPage - + Matrix ID - + Matrix ID e.g @joe:matrix.org - + e.g @joe:matrix.org Password - + Password + + + + Device name + Device name LOGIN - + LOGIN - - Empty password - - - - - MatrixClient - - - Wrong username or password - - - - - Login endpoint was not found on the server - + + Autodiscovery failed. Received malformed response. + Autodiscovery failed. Received malformed response. - An unknown error occured. Please try again. - + Autodiscovery failed. Unknown error when requesting .well-known. + Autodiscovery failed. Unknown error while requesting .well-known. - - Malformed response. Possibly not a Matrix server - + + The required endpoints were not found. Possibly not a Matrix server. + The required endpoints were not found. Possibly not a Matrix server. + + + + Received malformed response. Make sure the homeserver domain is valid. + Received malformed response. Make sure the homeserver domain is valid. + + + + An unknown error occured. Make sure the homeserver domain is valid. + An unknown error occured. Make sure the homeserver domain is valid. + + + + Empty password + Empty password MemberList - + Room members + Room members + + + + OK + OK + + + + MessageDelegate + + + redacted - - SHOW MORE + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: QuickSwitcher - + Search for a room... - + Search for a room… RegisterPage - + Username - + Username Password - + Password Password confirmation - + Password confirmation Home Server - + Home Server - + REGISTER - + REGISTER - + Invalid username - + Invalid username Password is not long enough (min 8 chars) - + Password is not long enough (min 8 chars) Passwords don't match - + Passwords don't match Invalid server name - + Invalid server name + + + + ReplyPopup + + + Logout + Logout + + + + RoomInfo + + + no version stored + no version stored RoomInfoListItem - + Leave room - + Leave room - + Accept - + Accept - + Decline - + Decline SideBarActions - + + User settings + User settings + + + Create new room - + Create new room Join a room + Join a room + + + + Start a new chat + Start a new chat + + + + Room directory + Room directory + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read TextInputWidget - - Write a message... - + + Send a file + Send a file - + + + Write a message... + Write a message… + + + + Send a message + Send a message + + + + Emoji + Emoji + + + Select a file - + Select a file All Files (*) + All Files (*) + + + + Connection lost. Nheko is trying to re-connect... + Connection lost. Nheko is trying to re-connect… + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + -- Encrypted Event (No keys found for decryption) -- + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + -- Decryption Error (failed to communicate with DB) -- + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + -- Decryption Error (failed to retrieve megolm keys from db) -- + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + -- Decryption Error (%1) -- + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + -- Encrypted Event (Unknown event type) -- + + + + Message redaction failed: %1 + Message redaction failed: %1 + + + + Save image + Save image + + + + Save video + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + %1%2 is typing + %1 and %2 are typing + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + + + + + TimelineView + + + Read receipts + Read receipts + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + Close + TopRoomBar - + + Room options + Room options + + + + Mentions + Mentions + + + Invite users - + Invite users Members - + Members Leave room - + Leave room Settings - + Settings TrayIcon - + Show - + Show Quit - + Quit - TypingDisplay + UserInfoWidget - - is typing - - - - - are typing - + + Logout + Logout UserSettingsPage - - User Settings - - - - + Minimize to tray + Minimize to tray + + + + Start in tray + Start in tray + + + + Group's sidebar + Group's sidebar + + + + Circular Avatars - - Start in tray + + Typing notifications + Typing notifications + + + + Read receipts + Read receipts + + + + Send messages as Markdown + + + Desktop notifications + Desktop notifications + + + + Scale factor + Scale factor + + + + Font size + Font size + + + + Font Family + Font Family + + + + Emoji Font Famly + Emoji Font Family + + + + Theme + Theme + + + + Device ID + Device ID + - Re-order rooms based on activity - + Device Fingerprint + Device Fingerprint - - Group's sidebar - + + Session Keys + Session Keys - - Typing notifications - + + IMPORT + IMPORT - - Read receipts - + + EXPORT + EXPORT - - Theme - + + ENCRYPTION + ENCRYPTION - + GENERAL - + GENERAL + + + + Open Sessions File + Open Sessions File + + + + + + + + + + + + + Error + Error + + + + + File Password + File Password + + + + Enter the passphrase to decrypt the file: + Enter the passphrase to decrypt the file: + + + + + The password cannot be empty + The password cannot be empty + + + + Enter passphrase to encrypt your session keys: + Enter passphrase to encrypt your session keys: + + + + File to save the exported session keys + File to save the exported session keys WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. - + Welcome to nheko! The desktop client for the Matrix protocol. Enjoy your stay! - + Enjoy your stay! - + REGISTER - + REGISTER - + LOGIN - + LOGIN + + + + descriptiveTime + + + Yesterday + Yesterday dialogs::CreateRoom - - CANCEL - + + Create room + Create room - + + Cancel + Cancel + + + Name - + Name Topic - + Topic Alias - + Alias Room Visibility - + Room Visibility + + + + Room Preset + Room Preset - Room Preset - - - - Direct Chat - + Direct Chat dialogs::InviteUsers - - CANCEL - + + Cancel + Cancel - + User ID to invite - + User ID to invite dialogs::JoinRoom - - CANCEL - + + Join + Join - + + Cancel + Cancel + + + Room ID or alias - + Room ID or alias dialogs::LeaveRoom - - CANCEL - + + Cancel + Cancel - + Are you sure you want to leave? - + Are you sure you want to leave? dialogs::Logout - - CANCEL - + + Cancel + Cancel - + Logout. Are you sure? - + Logout. Are you sure? dialogs::PreviewUploadOverlay - + Upload - + Upload Cancel - + Cancel - + Media type: %1 Media size: %2 - + Media type: %1 +Media size: %2 + dialogs::ReCaptcha - - CONFIRM - + + Cancel + Cancel - - CANCEL - + + Confirm + Confirm Solve the reCAPTCHA and press the confirm button - + Solve the reCAPTCHA and press the confirm button dialogs::ReadReceipts - + Read receipts - + Read receipts + + + + Close + Close + + + + dialogs::ReceiptItem + + + Today %1 + Today %1 + + + + Yesterday %1 + Yesterday %1 dialogs::RoomSettings - - CANCEL - - - - - Notifications - + + Settings + Settings - Muted - + Info + Info - + + Internal ID + Internal ID + + + + Room Version + Room Version + + + + Notifications + Notifications + + + + Muted + Muted + + + Mentions only - + Mentions only All messages - + All messages - + Room access - + Room access Anyone and guests - + Anyone and guests Anyone - + Anyone who knows the room link (no guests) Invited users - + Invited users + + + + Encryption + Encryption + + + + End-to-End Encryption + End-to-End Encryption + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + + + + Respond to key requests + Respond to key requests + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + + + + %n member(s) + + %n member + %n members + + + + + Failed to enable encryption: %1 + Failed to enable encryption: %1 + + + + Select an avatar + Select an avatar + + + + All Files (*) + All Files (*) + + + + The selected file is not an image + The selected file is not an image + + + + Error while reading file: %1 + Error while reading file: %1 + + + + + Failed to upload image: %s + Failed to upload image: %s + + + + dialogs::UserProfile + + + Ban the user from the room + Ban the user from the room + + + + Ignore messages from this user + Ignore messages from this user + + + + Kick the user from the room + Kick the user from the room + + + + Start a conversation + Start a conversation + + + + Devices + Devices emoji::Panel - + Smileys & People - Smileys & People + Smileys & People Animals & Nature - Animals & Nature + Animals & Nature Food & Drink - Food & Drink + Food & Drink Activity - Activity + Activity Travel & Places - Travel & Places + Travel & Places Objects - Objects + Objects Symbols - Symbols + Symbols Flags - Flags + Flags + + + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + This Room + + + + All Rooms + All Rooms + + + + utils + + + Unknown Message Type + Unknown Message Type diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts new file mode 100644 index 00000000..208209c6 --- /dev/null +++ b/resources/langs/nheko_fi.ts @@ -0,0 +1,1369 @@ + + + + + ChatPage + + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Failed to invite %1 to %2: %3 + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + + Failed to restore OLM account. Please login again. + OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. + + + + Failed to restore save data. Please login again. + Tallennettujen tietojen palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + Salausavainten lähetys epäonnistui. Palvelimen vastaus: %1 %2. Ole hyvä ja yritä uudelleen myöhemmin. + + + + + Please try to login again: %1 + Ole hyvä ja yritä kirjautua sisään uudelleen: %1 + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + Huoneen luominen epäonnistui: %1 + + + + Room %1 created + + + + + Failed to leave room: %1 + Huoneesta poistuminen epäonnistui: %1 + + + + CommunitiesListItem + + + All rooms + Kaikki huoneet + + + + Favourite rooms + Suosikkihuoneet + + + + Low priority rooms + Alhaisen prioriteetin huoneet + + + + + (tag) + (tag) + + + + (community) + (community) + + + + EditModal + + + Apply + Tallenna + + + + Cancel + Peruuta + + + + Name + Nimi + + + + Topic + Aihe + + + + EncryptionIndicator + + + Encrypted + + + + + InviteeItem + + + Remove + Poista + + + + LoginPage + + + Matrix ID + Matrix-tunnus + + + + e.g @joe:matrix.org + esim. @joe:matrix.org + + + + Password + Salasana + + + + Device name + Laitteen nimi + + + + LOGIN + KIRJAUDU + + + + Autodiscovery failed. Received malformed response. + Palvelimen tietojen hakeminen epäonnistui: virheellinen vastaus. + + + + Autodiscovery failed. Unknown error when requesting .well-known. + Palvelimen tietojen hakeminen epäonnistui: tuntematon virhe hakiessa .well-known -tiedostoa. + + + + The required endpoints were not found. Possibly not a Matrix server. + Vaadittuja päätepisteitä ei löydetty. Mahdollisesti ei Matrix-palvelin. + + + + Received malformed response. Make sure the homeserver domain is valid. + Vastaanotettiin virheellinen vastaus. Varmista, että kotipalvelimen osoite on pätevä. + + + + An unknown error occured. Make sure the homeserver domain is valid. + Tapahtui tuntematon virhe. Varmista, että kotipalvelimen osoite on pätevä. + + + + Empty password + Tyhjä salasana + + + + MemberList + + + Room members + Huoneen jäsenet + + + + OK + OK + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + + + + + QuickSwitcher + + + Search for a room... + Etsi huonetta… + + + + RegisterPage + + + Username + Käyttäjänimi + + + + Password + Salasana + + + + Password confirmation + Salasanan varmistus + + + + Home Server + Kotipalvelin + + + + REGISTER + REKISTERÖIDY + + + + Invalid username + Epäkelpo käyttäjänimi + + + + Password is not long enough (min 8 chars) + Salasana ei ole tarpeeksi pitkä (vähintään 8 merkkiä) + + + + Passwords don't match + Salasanat eivät täsmää + + + + Invalid server name + Epäkelpo palvelimen nimi + + + + ReplyPopup + + + Logout + Kirjaudu ulos + + + + RoomInfo + + + no version stored + ei tallennettua versiota + + + + RoomInfoListItem + + + Leave room + Poistu huoneesta + + + + Accept + Hyväksy + + + + Decline + Hylkää + + + + SideBarActions + + + User settings + Käyttäjäasetukset + + + + Create new room + Luo uusi huone + + + + Join a room + Liity huoneeseen + + + + Start a new chat + Aloita uusi keskustelu + + + + Room directory + Huoneluettelo + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read + + + + + TextInputWidget + + + Send a file + Lähetä tiedosto + + + + + Write a message... + Kirjoita viesti… + + + + Send a message + Lähetä viesti + + + + Emoji + Emoji + + + + Select a file + Valitse tiedosto + + + + All Files (*) + Kaikki tiedostot (*) + + + + Connection lost. Nheko is trying to re-connect... + Yhteys kadotettu. Nheko yrittää muodostaa yhteyttä uudelleen… + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + -- Salattu viesti (salauksen purkuavaimia ei löydetty) -- + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + -- Virhe purkaessa salausta (tietokannan kanssa kommunikointi epäonnistui) -- + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + -- Virhe purkaessa salausta (megolm-avaimien hakeminen tietokannasta epäonnistui) -- + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + -- Virhe purkaessa salausta (%1) -- + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + -- Salattu viesti (tuntematon viestityyppi) -- + + + + Message redaction failed: %1 + Viestin poisto epäonnistui: %1 + + + + Save image + Tallenna kuva + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + %1%2 kirjoittaa + %1 ja %2 kirjoittavat + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + + + + + TimelineView + + + Read receipts + Lukukuittaukset + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + Sulje + + + + TopRoomBar + + + Room options + Huonevaihtoehdot + + + + Mentions + Maininnat + + + + Invite users + Kutsu käyttäjiä + + + + Members + Jäsenet + + + + Leave room + Poistu huoneesta + + + + Settings + Asetukset + + + + TrayIcon + + + Show + Näytä + + + + Quit + Lopeta + + + + UserInfoWidget + + + Logout + Kirjaudu ulos + + + + UserSettingsPage + + + Minimize to tray + Pienennä ilmoitusalueelle + + + + Start in tray + Aloita ilmoitusalueella + + + + Group's sidebar + Ryhmäsivupalkki + + + + Circular Avatars + + + + + Typing notifications + Kirjoitusilmoitukset + + + + Read receipts + Lukukuittaukset + + + + Send messages as Markdown + + + + + Desktop notifications + Työpöytäilmoitukset + + + + Scale factor + Mittakerroin + + + + Font size + Fonttikoko + + + + Font Family + Fonttiperhe + + + + Emoji Font Famly + Emoji-fonttiperhe + + + + Theme + Teema + + + + Device ID + Laitteen tunnus + + + + Device Fingerprint + Laitteen sormenjälki + + + + Session Keys + Istunnon avaimet + + + + IMPORT + TUO + + + + EXPORT + VIE + + + + ENCRYPTION + SALAUS + + + + GENERAL + YLEISET ASETUKSET + + + + Open Sessions File + Avaa Istuntoavaintiedosto + + + + + + + + + + + + + Error + Virhe + + + + + File Password + Tiedoston Salasana + + + + Enter the passphrase to decrypt the file: + Anna salasana tiedoston salauksen purkamiseksi: + + + + + The password cannot be empty + Salasana ei voi olla tyhjä + + + + Enter passphrase to encrypt your session keys: + Anna salasana istuntoavaimien salaamiseksi: + + + + File to save the exported session keys + Tiedosto, johon viedyt istuntoavaimet tallennetaan + + + + WelcomePage + + + Welcome to nheko! The desktop client for the Matrix protocol. + Tervetuloa nhekoon! Työpöytäsovellus Matrix-protokollalle. + + + + Enjoy your stay! + + + + + REGISTER + REKISTERÖIDY + + + + LOGIN + KIRJAUDU + + + + descriptiveTime + + + Yesterday + Eilen + + + + dialogs::CreateRoom + + + Create room + Luo huone + + + + Cancel + Peruuta + + + + Name + Nimi + + + + Topic + Aihe + + + + Alias + Osoite + + + + Room Visibility + Huoneen näkyvyys + + + + Room Preset + Huoneen esiasetus + + + + Direct Chat + Suora keskustelu + + + + dialogs::InviteUsers + + + Cancel + Peruuta + + + + User ID to invite + Käyttäjätunnus kutsuttavaksi + + + + dialogs::JoinRoom + + + Join + Liity + + + + Cancel + Peruuta + + + + Room ID or alias + Huoneen tunnus tai osoite + + + + dialogs::LeaveRoom + + + Cancel + Peruuta + + + + Are you sure you want to leave? + Oletko varma, että haluat poistua? + + + + dialogs::Logout + + + Cancel + Peruuta + + + + Logout. Are you sure? + Kirjaudutaan ulos. Oletko varma? + + + + dialogs::PreviewUploadOverlay + + + Upload + Lähetä + + + + Cancel + Peruuta + + + + Media type: %1 +Media size: %2 + + Median tyyppi: %1 +Median koko: %2 + + + + + dialogs::ReCaptcha + + + Cancel + Peruuta + + + + Confirm + Vahvista + + + + Solve the reCAPTCHA and press the confirm button + Ratkaise reCAPTCHA ja paina varmista-nappia + + + + dialogs::ReadReceipts + + + Read receipts + Lukukuittaukset + + + + Close + Sulje + + + + dialogs::ReceiptItem + + + Today %1 + Tänään %1 + + + + Yesterday %1 + Eilen %1 + + + + dialogs::RoomSettings + + + Settings + Asetukset + + + + Info + Tiedot + + + + Internal ID + Sisäinen tunnus + + + + Room Version + Huoneen versio + + + + Notifications + Ilmoitukset + + + + Muted + Vaimennettu + + + + Mentions only + Vain maininnat + + + + All messages + Kaikki viestit + + + + Room access + Pääsy huoneeseen + + + + Anyone and guests + Kaikki (mukaanlukien vieraat) + + + + Anyone + Kaikki (poislukien vieraat) + + + + Invited users + Kutsutut käyttäjät + + + + Encryption + Salaus + + + + End-to-End Encryption + Päästä-päähän-salaus + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + Salaus on tällä hetkellä kokeellinen ja asiat saattavat mennä rikki odottamattomasti.<br>Huomaa, ettei sitä voi poistaa käytöstä jälkikäteen. + + + + Respond to key requests + Vastaa avainpyyntöihin + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + Pitäisikö asiakasohjelman palauttaa salausavaimet automaattisesti pyydettäessä. + Käytä varoen, tämä on väliaikainen vaihtoehto salausjärjestelmän testausta varten, + kunnes laitteiden vahvistus on valmis. + + + + %n member(s) + + %n käyttäjä + %n käyttäjää + + + + + Failed to enable encryption: %1 + Salauksen aktivointi epäonnistui: %1 + + + + Select an avatar + Valitse profiilikuva + + + + All Files (*) + Kaikki Tiedostot (*) + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + + Failed to upload image: %s + Kuvan lähetys epäonnistui: %s + + + + dialogs::UserProfile + + + Ban the user from the room + Anna käyttäjälle porttikielto huoneesta + + + + Ignore messages from this user + Jätä tämän käyttäjän viestit huomiotta + + + + Kick the user from the room + Potki käyttäjä huoneesta + + + + Start a conversation + Aloita keskustelu + + + + Devices + Laitteet + + + + emoji::Panel + + + Smileys & People + Hymiöt ja ihmiset + + + + Animals & Nature + Eläimet ja luonto + + + + Food & Drink + Ruoka ja juoma + + + + Activity + Aktiviteetti + + + + Travel & Places + Matkailu ja paikat + + + + Objects + Esineet + + + + Symbols + Symbolit + + + + Flags + Liput + + + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + + diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 4b995d07..6c97cc64 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -2,37 +2,150 @@ - AudioItem + ChatPage - - Save File - Enregistrer le fichier + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Failed to invite %1 to %2: %3 + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + + Failed to restore OLM account. Please login again. + + + + + Failed to restore save data. Please login again. + + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + + Please try to login again: %1 + + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + + + + + Room %1 created + + + + + Failed to leave room: %1 + - DateSeparator + CommunitiesListItem - - Today - Aujourd'hui + + All rooms + + + + + Favourite rooms + - Yesterday - Hier + Low priority rooms + + + + + + (tag) + + + + + (community) + EditModal - - APPLY - APPLIQUER + + Apply + - - CANCEL - ANNULER + + Cancel + Annuler @@ -46,25 +159,25 @@ - FileItem + EncryptionIndicator - - Save File - Enregistrer le fichier + + Encrypted + - ImageItem + InviteeItem - - Save image - Enregistrer l'image + + Remove + LoginPage - + Matrix ID Identifiant Matrix @@ -78,57 +191,105 @@ Password Mot de passe + + + Device name + + LOGIN CONNEXION - + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + + The required endpoints were not found. Possibly not a Matrix server. + + + + + Received malformed response. Make sure the homeserver domain is valid. + + + + + An unknown error occured. Make sure the homeserver domain is valid. + + + + Empty password Mot de passe vide - - MatrixClient - - - Wrong username or password - Mauvais nom d'utilisateur ou mot de passe - - - - Login endpoint was not found on the server - L'interface de connexion n'a pas pu être trouvée sur le serveur - - - - An unknown error occured. Please try again. - Une erreur inconnue s'est produite. Veuillez essayer à nouveau. - - - - Malformed response. Possibly not a Matrix server - La réponse du serveur est malformée. Il est possible qu'il ne s'agisse pas d'un serveur Matrix - - MemberList - + Room members Membres du salon - - SHOW MORE - MONTRER PLUS + + OK + + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + QuickSwitcher - + Search for a room... Chercher un salon… @@ -136,7 +297,7 @@ RegisterPage - + Username Nom d'utilisateur @@ -157,12 +318,12 @@ Serveur Matrix - + REGISTER S'ENREGISTRER - + Invalid username Nom d'utilisateur invalide @@ -182,20 +343,36 @@ Le nom du serveur est invalide + + ReplyPopup + + + Logout + + + + + RoomInfo + + + no version stored + + + RoomInfoListItem - + Leave room Quitter le salon - + Accept Accepter - + Decline Décliner @@ -203,7 +380,12 @@ SideBarActions - + + User settings + + + + Create new room Créer un nouveau salon @@ -212,16 +394,65 @@ Join a room Rejoindre un salon + + + Start a new chat + + + + + Room directory + + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read + + TextInputWidget - + + Send a file + + + + + Write a message... Écrivez un message... - + + Send a message + + + + + Emoji + + + + Select a file Sélectionnez un fichier @@ -230,11 +461,220 @@ All Files (*) Tous les types de fichiers (*) + + + Connection lost. Nheko is trying to re-connect... + + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + + + + + Message redaction failed: %1 + + + + + Save image + Enregistrer l'image + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + + + + + TimelineView + + + Read receipts + Accusés de lecture + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + + TopRoomBar - + + Room options + + + + + Mentions + + + + Invite users Inviter des utilisateurs @@ -257,7 +697,7 @@ TrayIcon - + Show Montrer @@ -268,70 +708,166 @@ - TypingDisplay + UserInfoWidget - - is typing - est en train d'écrire - - - - are typing - sont en train d'écrire + + Logout + UserSettingsPage - - User Settings - Paramètres utilisateur - - - + Minimize to tray Réduire à la barre des tâches - + Start in tray Démarrer dans la barre des tâches - - Re-order rooms based on activity - Ré-ordonner les salons en fonction de leur activité - - - + Group's sidebar Barre latérale des groupes - + + Circular Avatars + + + + Typing notifications Notifications d'écriture - + Read receipts Accusés de lecture - + + Send messages as Markdown + + + + + Desktop notifications + + + + + Scale factor + + + + + Font size + + + + + Font Family + + + + + Emoji Font Famly + + + + Theme Thème - + + Device ID + + + + + Device Fingerprint + + + + + Session Keys + + + + + IMPORT + + + + + EXPORT + + + + + ENCRYPTION + + + + GENERAL GÉNÉRAL + + + Open Sessions File + + + + + + + + + + + + + + Error + + + + + + File Password + + + + + Enter the passphrase to decrypt the file: + + + + + + The password cannot be empty + + + + + Enter passphrase to encrypt your session keys: + + + + + File to save the exported session keys + + WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. Bienvenue sur nheko ! Le client de bureau pour le protocole Matrix. @@ -341,25 +877,38 @@ Bon séjour ! - + REGISTER S'ENREGISTRER - + LOGIN CONNEXION + + descriptiveTime + + + Yesterday + + + dialogs::CreateRoom - - CANCEL - ANNULER + + Create room + - + + Cancel + Annuler + + + Name Nom @@ -379,12 +928,12 @@ Visibilité du salon - + Room Preset Préréglage du salon - + Direct Chat Discussion directe @@ -392,12 +941,12 @@ dialogs::InviteUsers - - CANCEL - ANNULER + + Cancel + Annuler - + User ID to invite Identifiant d'utilisateur à inviter @@ -405,12 +954,17 @@ dialogs::JoinRoom - - CANCEL - ANNULER + + Join + - + + Cancel + Annuler + + + Room ID or alias Identifiant ou alias du salon @@ -418,12 +972,12 @@ dialogs::LeaveRoom - - CANCEL - ANNULER + + Cancel + Annuler - + Are you sure you want to leave? Êtes-vous sûr·e de vouloir quitter ? @@ -431,12 +985,12 @@ dialogs::Logout - - CANCEL - ANNULER + + Cancel + Annuler - + Logout. Are you sure? Déconnexion. Êtes-vous sûr·e ? @@ -444,7 +998,7 @@ dialogs::PreviewUploadOverlay - + Upload Envoyer @@ -454,7 +1008,7 @@ Annuler - + Media type: %1 Media size: %2 @@ -466,14 +1020,14 @@ Taille du média : %2 dialogs::ReCaptcha - - CONFIRM - CONFIRMER + + Cancel + Annuler - - CANCEL - ANNULER + + Confirm + @@ -484,30 +1038,63 @@ Taille du média : %2 dialogs::ReadReceipts - + Read receipts Accusés de lecture + + + Close + + + + + dialogs::ReceiptItem + + + Today %1 + + + + + Yesterday %1 + + dialogs::RoomSettings - - CANCEL - ANNULER + + Settings + Paramètres - + + Info + + + + + Internal ID + + + + + Room Version + + + + Notifications Notifications - + Muted Silencieux - + Mentions only Uniquement les mentions @@ -517,7 +1104,7 @@ Taille du média : %2 Tous les messages - + Room access Accès au salon @@ -536,11 +1123,105 @@ Taille du média : %2 Invited users Utilisateurs invités + + + Encryption + + + + + End-to-End Encryption + + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + + + + + Respond to key requests + + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + + + + + %n member(s) + + + + + + + + Failed to enable encryption: %1 + + + + + Select an avatar + + + + + All Files (*) + Tous les types de fichiers (*) + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + + Failed to upload image: %s + + + + + dialogs::UserProfile + + + Ban the user from the room + + + + + Ignore messages from this user + + + + + Kick the user from the room + + + + + Start a conversation + + + + + Devices + + emoji::Panel - + Smileys & People Smileys & Personnes @@ -580,4 +1261,108 @@ Taille du média : %2 Drapeaux + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts new file mode 100644 index 00000000..180c060e --- /dev/null +++ b/resources/langs/nheko_ja.ts @@ -0,0 +1,1368 @@ + + + + + ChatPage + + + Failed to invite user: %1 + ユーザーを招待できませんでした: %1 + + + + + Invited user: %1 + 招待されたユーザー: %1 + + + + Failed to invite %1 to %2: %3 + %2に%1を招待できませんでした: %3 + + + + Failed to kick %1 to %2: %3 + %2に%1を一時的に追放できませんでした: %3 + + + + Kicked user: %1 + 一時的に追放されたユーザー: %1 + + + + Failed to ban %1 in %2: %3 + %2で%1を永久追放できませんでした: %3 + + + + Banned user: %1 + 永久追放されたユーザー: %1 + + + + Failed to unban %1 in %2: %3 + %2で%1の永久追放を解除できませんでした: %3 + + + + Unbanned user: %1 + 永久追放を解除されたユーザー: %1 + + + + Failed to upload media. Please try again. + メディアをアップロードできませんでした。やり直して下さい。 + + + + Failed to restore OLM account. Please login again. + OLMアカウントを復元できませんでした。もう一度ログインして下さい。 + + + + Failed to restore save data. Please login again. + セーブデータを復元できませんでした。もう一度ログインして下さい。 + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + 暗号化鍵を設定できませんでした。サーバーの応答: %1 %2. 後でやり直して下さい。 + + + + + Please try to login again: %1 + もう一度ログインしてみて下さい: %1 + + + + Failed to join room: %1 + 部屋に参加できませんでした: %1 + + + + You joined the room + 部屋に参加しました + + + + Failed to remove invite: %1 + 招待を削除できませんでした: %1 + + + + Room creation failed: %1 + 部屋を作成できませんでした: %1 + + + + Room %1 created + 部屋 %1 を作成しました + + + + Failed to leave room: %1 + 部屋から出られませんでした: %1 + + + + CommunitiesListItem + + + All rooms + 全ての部屋 + + + + Favourite rooms + お気に入りの部屋 + + + + Low priority rooms + 優先度の低い部屋 + + + + + (tag) + (タグ) + + + + (community) + (コミュニティー) + + + + EditModal + + + Apply + 適用 + + + + Cancel + キャンセル + + + + Name + 名前 + + + + Topic + 話題 + + + + EncryptionIndicator + + + Encrypted + 暗号化されています + + + + InviteeItem + + + Remove + 削除 + + + + LoginPage + + + Matrix ID + Matrix ID + + + + e.g @joe:matrix.org + 例 @joe:matrix.org + + + + Password + パスワード + + + + Device name + デバイス名 + + + + LOGIN + ログイン + + + + Autodiscovery failed. Received malformed response. + 自動検出できませんでした。不正な形式の応答を受信しました。 + + + + Autodiscovery failed. Unknown error when requesting .well-known. + 自動検出できませんでした。.well-known要求時の不明なエラー。 + + + + The required endpoints were not found. Possibly not a Matrix server. + 必要な端点が見つかりません。Matrixサーバーではないかもしれません。 + + + + Received malformed response. Make sure the homeserver domain is valid. + 不正な形式の応答を受信しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 + + + + An unknown error occured. Make sure the homeserver domain is valid. + 不明なエラーが発生しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 + + + + Empty password + パスワードが入力されていません + + + + MemberList + + + Room members + 部屋の参加者 + + + + OK + OK + + + + MessageDelegate + + + redacted + 編集済み + + + + Encryption enabled + 暗号化が有効です + + + + room name changed to: %1 + 部屋名が変更されました: %1 + + + + removed room name + 部屋名が削除されました + + + + topic changed to: %1 + 話題が変更されました: %1 + + + + removed topic + 話題が削除されました + + + + Placeholder + + + unimplemented event: + 未実装のイベント: + + + + QuickSwitcher + + + Search for a room... + 部屋を探す... + + + + RegisterPage + + + Username + ユーザー名 + + + + Password + パスワード + + + + Password confirmation + パスワード確認 + + + + Home Server + ホームサーバー + + + + REGISTER + 登録 + + + + Invalid username + 無効なユーザー名です + + + + Password is not long enough (min 8 chars) + パスワード長が不足しています (最小8文字) + + + + Passwords don't match + パスワードが一致しません + + + + Invalid server name + 無効なサーバー名です + + + + ReplyPopup + + + Logout + ログアウト + + + + RoomInfo + + + no version stored + バージョンが保存されていません + + + + RoomInfoListItem + + + Leave room + 部屋を出る + + + + Accept + 容認 + + + + Decline + 拒否 + + + + SideBarActions + + + User settings + ユーザー設定 + + + + Create new room + 新しい部屋を作成 + + + + Join a room + 部屋に参加 + + + + Start a new chat + 新しいチャットを開始 + + + + Room directory + 部屋一覧 + + + + StatusIndicator + + + Failed + 失敗 + + + + Sent + 送信済み + + + + Received + 受信済み + + + + Read + 既読 + + + + TextInputWidget + + + Send a file + ファイルを送信 + + + + + Write a message... + メッセージを書く... + + + + Send a message + メッセージを送信 + + + + Emoji + 絵文字 + + + + Select a file + ファイルを選択 + + + + All Files (*) + 全てのファイル (*) + + + + Connection lost. Nheko is trying to re-connect... + 接続が切れました。Nhekoは再接続を試みています... + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + -- 暗号化イベント (復号鍵が見つかりません) -- + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + -- 復号エラー (データベースと通信できませんでした) -- + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + -- 復号エラー (データベースからmegolm鍵を取得できませんでした) -- + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + -- 復号エラー (%1) -- + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + -- 暗号化イベント (不明なイベント型です) -- + + + + Message redaction failed: %1 + メッセージを編集できませんでした: %1 + + + + Save image + 画像を保存 + + + + Save video + 動画を保存 + + + + Save audio + 音声を保存 + + + + Save file + ファイルを保存 + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + %1%2が入力しています + %1と%2が入力しています + + + + + %1 was invited. + %1が招待されました。 + + + + %1 changed their display name and avatar. + %1が表示名とアバターを変更しました。 + + + + %1 changed their display name. + %1が表示名を変更しました。 + + + + %1 changed their avatar. + %1がアバターを変更しました。 + + + + %1 joined. + %1が参加しました。 + + + + %1 rejected their invite. + %1が招待を拒否しました。 + + + + Revoked the invite to %1. + %1への招待を取り消しました。 + + + + %1 left the room. + %1は退室しました。 + + + + Kicked %1. + %1を一時的に追放しました。 + + + + Unbanned %1 + %1の永久追放を解除しました + + + + %1 redacted their knock. + %1がノックを編集しました。 + + + + Rejected the knock from %1. + %1からのノックを拒否しました。 + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + 退出済みの%1が退出しました! + + + + %1 was banned. + %1が永久追放されました。 + + + + %1 knocked. + %1がノックしました。 + + + + TimelineRow + + + Reply + 返信 + + + + Options + オプション + + + + TimelineView + + + Read receipts + 開封確認 + + + + Mark as read + 既読にする + + + + View raw message + ソースを見る + + + + Redact message + メッセージを編集 + + + + Save as + 名前を付けて保存 + + + + No room open + 部屋が開いていません + + + + Close + 閉じる + + + + TopRoomBar + + + Room options + 部屋のオプション + + + + Mentions + メンション + + + + Invite users + ユーザーを招待 + + + + Members + メンバー + + + + Leave room + 退室する + + + + Settings + 設定 + + + + TrayIcon + + + Show + 表示 + + + + Quit + 終了 + + + + UserInfoWidget + + + Logout + ログアウト + + + + UserSettingsPage + + + Minimize to tray + トレイへ最小化 + + + + Start in tray + トレイで起動 + + + + Group's sidebar + グループサイドバー + + + + Circular Avatars + 円形アバター + + + + Typing notifications + 入力状態の通知 + + + + Read receipts + 開封確認 + + + + Send messages as Markdown + メッセージをMarkdownとして送信 + + + + Desktop notifications + デスクトップ通知 + + + + Scale factor + 尺度係数 + + + + Font size + フォントサイズ + + + + Font Family + フォントファミリー + + + + Emoji Font Famly + 絵文字のフォントファミリー + + + + Theme + テーマ + + + + Device ID + デバイスID + + + + Device Fingerprint + デバイスの指紋 + + + + Session Keys + セッション鍵 + + + + IMPORT + インポート + + + + EXPORT + エクスポート + + + + ENCRYPTION + 暗号化 + + + + GENERAL + 全般 + + + + Open Sessions File + セッションファイルを開く + + + + + + + + + + + + + Error + エラー + + + + + File Password + ファイルのパスワード + + + + Enter the passphrase to decrypt the file: + ファイルを復号するためのパスフレーズを入力して下さい: + + + + + The password cannot be empty + パスワードを空にはできません + + + + Enter passphrase to encrypt your session keys: + セッション鍵を暗号化するためのパスフレーズを入力して下さい: + + + + File to save the exported session keys + エクスポートされたセッション鍵を保存するファイル + + + + WelcomePage + + + Welcome to nheko! The desktop client for the Matrix protocol. + Matrixプロトコルのデスクトップクライアント、nhekoへようこそ! + + + + Enjoy your stay! + 会話を楽しんで下さい! + + + + REGISTER + 登録 + + + + LOGIN + ログイン + + + + descriptiveTime + + + Yesterday + 昨日 + + + + dialogs::CreateRoom + + + Create room + 部屋を作成 + + + + Cancel + キャンセル + + + + Name + 名前 + + + + Topic + 話題 + + + + Alias + 別名 + + + + Room Visibility + 部屋の可視性 + + + + Room Preset + 部屋の初期値 + + + + Direct Chat + ダイレクトメッセージ + + + + dialogs::InviteUsers + + + Cancel + キャンセル + + + + User ID to invite + 招待するユーザーのID + + + + dialogs::JoinRoom + + + Join + 参加 + + + + Cancel + キャンセル + + + + Room ID or alias + 部屋のID又は別名 + + + + dialogs::LeaveRoom + + + Cancel + キャンセル + + + + Are you sure you want to leave? + 本当に退出しますか? + + + + dialogs::Logout + + + Cancel + キャンセル + + + + Logout. Are you sure? + 本当にログアウトしますか? + + + + dialogs::PreviewUploadOverlay + + + Upload + アップロード + + + + Cancel + キャンセル + + + + Media type: %1 +Media size: %2 + + メディアの種類: %1 +メディアのサイズ: %2 + + + + + dialogs::ReCaptcha + + + Cancel + キャンセル + + + + Confirm + 確認 + + + + Solve the reCAPTCHA and press the confirm button + reCAPTCHAに解答して、確認ボタンを押して下さい + + + + dialogs::ReadReceipts + + + Read receipts + 開封確認 + + + + Close + 閉じる + + + + dialogs::ReceiptItem + + + Today %1 + 今日 %1 + + + + Yesterday %1 + 昨日 %1 + + + + dialogs::RoomSettings + + + Settings + 設定 + + + + Info + 情報 + + + + Internal ID + 内部ID + + + + Room Version + 部屋のバージョン + + + + Notifications + 通知 + + + + Muted + ミュート + + + + Mentions only + メンションのみ + + + + All messages + 全てのメッセージ + + + + Room access + 部屋のアクセス権 + + + + Anyone and guests + 登録ユーザーとゲスト + + + + Anyone + 登録ユーザーのみ + + + + Invited users + 招待された登録ユーザーのみ + + + + Encryption + 暗号化 + + + + End-to-End Encryption + エンドツーエンド暗号化 + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + 暗号化機能は実験段階にあるので、予期せずに壊れるかもしれません。 <br>この機能を後から無効にできないことに注意して下さい。 + + + + Respond to key requests + 鍵の要求に応答する + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + クライアントがセッション鍵を要求された際に、自動的に応答するべきか否か。 +デバイス検証機能が実装されるまでのE2E暗号化を検査するための一時的な方法なので、 +これを利用する際は注意して下さい。 + + + + %n member(s) + + %n人 + + + + + Failed to enable encryption: %1 + 暗号化を有効にできませんでした: %1 + + + + Select an avatar + アバターを選択 + + + + All Files (*) + 全てのファイル (*) + + + + The selected file is not an image + 選択したファイルは画像ではありません + + + + Error while reading file: %1 + ファイルの読み込み時にエラーが発生しました: %1 + + + + + Failed to upload image: %s + 画像をアップロードできませんでした: %s + + + + dialogs::UserProfile + + + Ban the user from the room + ユーザーを部屋から永久追放する + + + + Ignore messages from this user + このユーザーからのメッセージを無視する + + + + Kick the user from the room + ユーザーを部屋から一時的に追放する + + + + Start a conversation + 会話を始める + + + + Devices + デバイス + + + + emoji::Panel + + + Smileys & People + 表情と人 + + + + Animals & Nature + 動物と自然 + + + + Food & Drink + 飲食物 + + + + Activity + 活動 + + + + Travel & Places + 旅行と場所 + + + + Objects + + + + + Symbols + 記号 + + + + Flags + + + + + message-description sent: + + + You sent an audio clip + 音声データを送信しました + + + + %1 sent an audio clip + %1が音声データを送信しました + + + + You sent an image + 画像を送信しました + + + + %1 sent an image + %1が画像を送信しました + + + + You sent a file + ファイルを送信しました + + + + %1 sent a file + %1がファイルを送信しました + + + + You sent a video + 動画を送信しました + + + + %1 sent a video + %1が動画を送信しました + + + + You sent a sticker + ステッカーを送信しました + + + + %1 sent a sticker + %1がステッカーを送信しました + + + + You sent a notification + 通知を送信しました + + + + %1 sent a notification + %1が通知を送信しました + + + + You: %1 + あなた: %1 + + + + %1: %2 + %1: %2 + + + + You sent an encrypted message + 暗号化されたメッセージを送信しました + + + + %1 sent an encrypted message + %1が暗号化されたメッセージを送信しました + + + + popups::UserMentions + + + This Room + この部屋 + + + + All Rooms + 全ての部屋 + + + + utils + + + Unknown Message Type + 不明なメッセージ型です + + + diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index c13ab123..dba70d50 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -2,40 +2,153 @@ - AudioItem + ChatPage - - Save File - Bestand opslaan + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Failed to invite %1 to %2: %3 + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + + Failed to restore OLM account. Please login again. + + + + + Failed to restore save data. Please login again. + + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + + Please try to login again: %1 + + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + + + + + Room %1 created + + + + + Failed to leave room: %1 + - DateSeparator + CommunitiesListItem - - Today - Vandaag + + All rooms + + + + + Favourite rooms + - Yesterday - Gisteren + Low priority rooms + + + + + + (tag) + + + + + (community) + EditModal - - APPLY - TOEPASSEN + + Apply + - - CANCEL - ANNULEREN + + Cancel + Annuleren - + Name Naam @@ -46,25 +159,25 @@ - FileItem + EncryptionIndicator - - Save File - Bestand opslaan + + Encrypted + - ImageItem + InviteeItem - - Save image - Afbeelding opslaan + + Remove + LoginPage - + Matrix ID Matrix-id @@ -78,57 +191,105 @@ Password Wachtwoord + + + Device name + + LOGIN INLOGGEN - + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + + The required endpoints were not found. Possibly not a Matrix server. + + + + + Received malformed response. Make sure the homeserver domain is valid. + + + + + An unknown error occured. Make sure the homeserver domain is valid. + + + + Empty password Leeg wachtwoord - - MatrixClient - - - Wrong username or password - Verkeerde gebruikersnaam of wachtwoord - - - - Login endpoint was not found on the server - Het inlog-endpoint is niet aangetroffen op de server - - - - An unknown error occured. Please try again. - Er is een onbekende fout opgetreden. Probeer het opnieuw. - - - - Malformed response. Possibly not a Matrix server - Onjuist antwoord ontvangen: dit is mogelijk geen Matrix server - - MemberList - + Room members Kamerleden - - SHOW MORE - MEER TONEN + + OK + + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + QuickSwitcher - + Search for a room... Zoek een kamer... @@ -136,7 +297,7 @@ RegisterPage - + Username Gebruikersnaam @@ -156,12 +317,12 @@ Thuisserver - + REGISTER REGISTREREN - + Invalid username Ongeldige gebruikersnaam @@ -181,20 +342,36 @@ Ongeldige servernaam + + ReplyPopup + + + Logout + + + + + RoomInfo + + + no version stored + + + RoomInfoListItem - + Leave room Kamer verlaten - + Accept Accepteren - + Decline Afwijzen @@ -202,7 +379,12 @@ SideBarActions - + + User settings + + + + Create new room Nieuwe kamer creëren @@ -211,16 +393,65 @@ Join a room Kamer betreden + + + Start a new chat + + + + + Room directory + + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read + + TextInputWidget - + + Send a file + + + + + Write a message... Typ een bericht... - + + Send a message + + + + + Emoji + + + + Select a file Kies een bestand @@ -229,11 +460,220 @@ All Files (*) Alle bestanden (*) + + + Connection lost. Nheko is trying to re-connect... + + + + + TimelineModel + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + + + + + Message redaction failed: %1 + + + + + Save image + Afbeelding opslaan + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + + + + + TimelineView + + + Read receipts + Leesbevestigingen + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + + TopRoomBar - + + Room options + + + + + Mentions + + + + Invite users Gebruikers uitnodigen @@ -256,7 +696,7 @@ TrayIcon - + Show Tonen @@ -267,70 +707,166 @@ - TypingDisplay + UserInfoWidget - - is typing - is aan het typen - - - - are typing - zijn aan het typen + + Logout + UserSettingsPage - - User Settings - Gebruikersinstellingen - - - + Minimize to tray Minimaliseren naar systeemvak - + Start in tray Geminimaliseerd opstarten - - Re-order rooms based on activity - Kamers herordenen op basis van activiteit - - - + Group's sidebar Zijbalk van groep - + + Circular Avatars + + + + Typing notifications Meldingen bij typen van berichten - + Read receipts Leesbevestigingen - + + Send messages as Markdown + + + + + Desktop notifications + + + + + Scale factor + + + + + Font size + + + + + Font Family + + + + + Emoji Font Famly + + + + Theme Thema - + + Device ID + + + + + Device Fingerprint + + + + + Session Keys + + + + + IMPORT + + + + + EXPORT + + + + + ENCRYPTION + + + + GENERAL ALGEMEEN + + + Open Sessions File + + + + + + + + + + + + + + Error + + + + + + File Password + + + + + Enter the passphrase to decrypt the file: + + + + + + The password cannot be empty + + + + + Enter passphrase to encrypt your session keys: + + + + + File to save the exported session keys + + WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. Welkom bij nheko! Dé computerclient voor het Matrix-protocol. @@ -340,25 +876,38 @@ Geniet van je verblijf! - + REGISTER REGISTREREN - + LOGIN INLOGGEN + + descriptiveTime + + + Yesterday + + + dialogs::CreateRoom - - CANCEL - ANNULEREN + + Create room + - + + Cancel + Annuleren + + + Name Naam @@ -378,12 +927,12 @@ Kamerzichtbaarheid - + Room Preset Kamer-voorinstellingen - + Direct Chat Directe chat @@ -391,12 +940,12 @@ dialogs::InviteUsers - - CANCEL - ANNULEREN + + Cancel + Annuleren - + User ID to invite Uit te nodigen gebruikers-id @@ -404,12 +953,17 @@ dialogs::JoinRoom - - CANCEL - ANNULEREN + + Join + - + + Cancel + Annuleren + + + Room ID or alias Kamer-id of alias @@ -417,12 +971,12 @@ dialogs::LeaveRoom - - CANCEL - ANNULEREN + + Cancel + Annuleren - + Are you sure you want to leave? Weet je zeker dat je wilt vertrekken? @@ -430,12 +984,12 @@ dialogs::Logout - - CANCEL - ANNULEREN + + Cancel + Annuleren - + Logout. Are you sure? Uitloggen. Weet je het zeker? @@ -443,7 +997,7 @@ dialogs::PreviewUploadOverlay - + Upload Uploaden @@ -453,7 +1007,7 @@ Annuleren - + Media type: %1 Media size: %2 @@ -465,14 +1019,14 @@ Mediagrootte: %2 dialogs::ReCaptcha - - CONFIRM - BEVESTIGEN + + Cancel + Annuleren - - CANCEL - ANNULEREN + + Confirm + @@ -483,30 +1037,63 @@ Mediagrootte: %2 dialogs::ReadReceipts - + Read receipts Leesbevestigingen + + + Close + + + + + dialogs::ReceiptItem + + + Today %1 + + + + + Yesterday %1 + + dialogs::RoomSettings - - CANCEL - ANNULEREN + + Settings + Instellingen - + + Info + + + + + Internal ID + + + + + Room Version + + + + Notifications Meldingen - + Muted Gedempt - + Mentions only Alleen vermeldingen @@ -516,7 +1103,7 @@ Mediagrootte: %2 Alle berichten - + Room access Kamertoegang @@ -535,11 +1122,105 @@ Mediagrootte: %2 Invited users Uitgenodigde gebruikers + + + Encryption + + + + + End-to-End Encryption + + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + + + + + Respond to key requests + + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + + + + + %n member(s) + + + + + + + + Failed to enable encryption: %1 + + + + + Select an avatar + + + + + All Files (*) + Alle bestanden (*) + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + + Failed to upload image: %s + + + + + dialogs::UserProfile + + + Ban the user from the room + + + + + Ignore messages from this user + + + + + Kick the user from the room + + + + + Start a conversation + + + + + Devices + + emoji::Panel - + Smileys & People Smileys en mensen @@ -579,4 +1260,108 @@ Mediagrootte: %2 Vlaggen + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 2d908653..fd23b216 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -1,38 +1,61 @@ - - AudioItem - - - Save File - Zapisz plik - - ChatPage - - Failed to upload image. Please try again. - Nie udało się wysłać obrazu. Spróbuj ponownie. + + Failed to invite user: %1 + - - Failed to upload file. Please try again. - Nie udało się wysłać pliku. Spróbuj ponownie. + + + Invited user: %1 + - - Failed to upload audio. Please try again. - Nie udało się wysłać pliku dźwiękowego. Spróbuj ponownie. + + Failed to invite %1 to %2: %3 + - - Failed to upload video. Please try again. - Nie udało się wysłać filmu. Spróbuj ponownie. + + Failed to kick %1 to %2: %3 + - + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + Failed to restore OLM account. Please login again. Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie. @@ -42,49 +65,90 @@ Nie udało się przywrócić zapisanych danych. Spróbuj zalogować się ponownie. - - + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + Please try to login again: %1 Spróbuj zalogować się ponownie: %1 - + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + Room creation failed: %1 Tworzenie pokoju nie powiodło się: %1 - + + Room %1 created + + + + Failed to leave room: %1 Nie udało się opuścić pokoju: %1 - DateSeparator + CommunitiesListItem - - Today - Dzisiaj + + All rooms + + + + + Favourite rooms + - Yesterday - Wczoraj + Low priority rooms + + + + + + (tag) + + + + + (community) + EditModal - APPLY - ZASTOSUJ + Apply + - - CANCEL - ANULUJ + + Cancel + Anuluj - + Name Nazwa @@ -95,25 +159,25 @@ - FileItem + EncryptionIndicator - - Save File - Zapisz plik + + Encrypted + - ImageItem + InviteeItem - - Save image - Zapisz obraz + + Remove + LoginPage - + Matrix ID ID Matrixa @@ -138,7 +202,17 @@ ZALOGUJ - + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + The required endpoints were not found. Possibly not a Matrix server. Nie odnaleziono wymaganych punktów końcowych. To może nie być serwer Matriksa. @@ -161,20 +235,61 @@ MemberList - + Room members Członkowie pokoju - - SHOW MORE - POKAŻ WIĘCEJ + + OK + + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + QuickSwitcher - + Search for a room... Wyszukaj pokoju… @@ -182,7 +297,7 @@ RegisterPage - + Username Nazwa użytkownika @@ -202,7 +317,7 @@ Serwer domowy - + REGISTER ZAREJESTRUJ @@ -227,20 +342,36 @@ Nieprawidłowa nazwa serwera + + ReplyPopup + + + Logout + Wyloguj + + + + RoomInfo + + + no version stored + + + RoomInfoListItem - + Leave room Opuść pokój - + Accept Akceptuj - + Decline Odrzuć @@ -248,7 +379,7 @@ SideBarActions - + User settings Ustawienia użytkownika @@ -276,41 +407,41 @@ StatusIndicator - - Encrypted - Szyfrowana + + Failed + - - Delivered - Dostarczono - - - - Seen - Wyświetlona - - - + Sent - Wysłana + + + + + Received + + + + + Read + TextInputWidget - + Send a file Wyślij plik - - + + Write a message... Napisz wiadomość… - + Send a message Wyślij wiadomość @@ -320,7 +451,7 @@ Emoji - + Select a file Wybierz plik @@ -336,30 +467,214 @@ - TimelineItem + TimelineModel - + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + + + + Message redaction failed: %1 - Redagowanie wiadomości nie powiodło się: %1 + Redagowanie wiadomości nie powiodło się: %1 + + + + Save image + Zapisz obraz + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + TimelineView - - Encryption is enabled - Szyfrowanie jest włączone + + Read receipts + Potwierdzenia przeczytania + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + TopRoomBar - + Room options Ustawienia pokoju - + + Mentions + + + + Invite users Zaproś użytkowników @@ -382,7 +697,7 @@ TrayIcon - + Show Pokaż @@ -392,23 +707,10 @@ Zakończ - - TypingDisplay - - - is typing - pisze - - - - are typing - piszą - - UserInfoWidget - + Logout Wyloguj @@ -416,75 +718,156 @@ UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań - + Start in tray Rozpocznij na pasku zadań - - Re-order rooms based on activity - Porządkuj pokoje na podstawie aktywności - - - + Group's sidebar Pasek boczny grupy - + + Circular Avatars + + + + Typing notifications Powiadomienia o pisaniu - + Read receipts Potwierdzenia przeczytania - + + Send messages as Markdown + + + + Desktop notifications Powiadomienia na pulpicie - - Scale factor (requires restart) - Czynnik skalowania (wymaga ponownego uruchomienia) + + Scale factor + - + + Font size + + + + + Font Family + + + + + Emoji Font Famly + + + + Theme Motyw - + Device ID ID urządzenia - + Device Fingerprint Odcisk palca urządzenia + + + Session Keys + + + + + IMPORT + + + + + EXPORT + + ENCRYPTION SZYFROWANIE - + GENERAL OGÓLNE + + + Open Sessions File + + + + + + + + + + + + + + Error + + + + + + File Password + + + + + Enter the passphrase to decrypt the file: + + + + + + The password cannot be empty + + + + + Enter passphrase to encrypt your session keys: + + + + + File to save the exported session keys + + WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. Witamy w nheko! Desktopowy klient protokołu Matrix. @@ -494,25 +877,38 @@ Udanego pobytu! - + REGISTER ZAREJESTRUJ SIĘ - + LOGIN ZALOGUJ SIĘ + + descriptiveTime + + + Yesterday + + + dialogs::CreateRoom - - CANCEL - ANULUJ + + Create room + - + + Cancel + Anuluj + + + Name Nazwa @@ -545,9 +941,9 @@ dialogs::InviteUsers - - CANCEL - ANULUJ + + Cancel + Anuluj @@ -558,12 +954,17 @@ dialogs::JoinRoom - - CANCEL - ANULUJ + + Join + - + + Cancel + Anuluj + + + Room ID or alias ID pokoju lub alias @@ -571,12 +972,12 @@ dialogs::LeaveRoom - - CANCEL - ANULUJ + + Cancel + Anuluj - + Are you sure you want to leave? Czy na pewno chcesz wyjść? @@ -584,12 +985,12 @@ dialogs::Logout - - CANCEL - ANULUJ + + Cancel + Anuluj - + Logout. Are you sure? Czy na pewno chcesz wylogować się? @@ -597,7 +998,7 @@ dialogs::PreviewUploadOverlay - + Upload Wyślij @@ -607,7 +1008,7 @@ Anuluj - + Media type: %1 Media size: %2 @@ -619,14 +1020,14 @@ Rozmiar multimediów: %2 dialogs::ReCaptcha - - CONFIRM - POTWIERDŹ + + Cancel + Anuluj - - CANCEL - ANULUJ + + Confirm + @@ -637,15 +1038,33 @@ Rozmiar multimediów: %2 dialogs::ReadReceipts - + Read receipts Potwierdzenia przeczytania + + + Close + + + + + dialogs::ReceiptItem + + + Today %1 + + + + + Yesterday %1 + + dialogs::RoomSettings - + Settings Ustawienia @@ -655,22 +1074,27 @@ Rozmiar multimediów: %2 Informacje - + Internal ID Wewnętrzne ID + + + Room Version + + Notifications Powiadomienia - + Muted Wyciszone - + Mentions only Tylko wspomnienia @@ -680,7 +1104,7 @@ Rozmiar multimediów: %2 Wszystkie wiadomości - + Room access Dostęp do pokoju @@ -729,7 +1153,7 @@ Rozmiar multimediów: %2 do testowania implementacji E2E, zanim weryfikacja urządzeń będzie ukończona. - + %n member(s) %n członek @@ -738,12 +1162,12 @@ Rozmiar multimediów: %2 - + Failed to enable encryption: %1 Nie udało się włączyć szyfrowania: %1 - + Select an avatar Wybierz awatar @@ -754,13 +1178,13 @@ Rozmiar multimediów: %2 - The selected media is not an image - Wybrany plik multimedialny nie jest obrazem + The selected file is not an image + - Error while reading media: %1 - Błąd odczytywania pliku: %1 + Error while reading file: %1 + @@ -772,12 +1196,12 @@ Rozmiar multimediów: %2 dialogs::UserProfile - + Ban the user from the room Zablokuj użytkownika w tym pokoju - + Ignore messages from this user Ignoruj wiadomości od tego użytkownika @@ -787,12 +1211,12 @@ Rozmiar multimediów: %2 Wyrzuć użytkownika z tego pokoju - + Start a conversation Rozpocznij rozmowę - + Devices Urządzenia @@ -800,7 +1224,7 @@ Rozmiar multimediów: %2 emoji::Panel - + Smileys & People Twarze i ludzie @@ -840,4 +1264,108 @@ Rozmiar multimediów: %2 Flagi + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 297611ab..e3817db0 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -1,38 +1,61 @@ - - AudioItem - - - Save File - Сохранить файл - - ChatPage - - Failed to upload image. Please try again. - Не удалось загрузить изображение. Пожалуйста, попробуйте еще раз. + + Failed to invite user: %1 + - - Failed to upload file. Please try again. - Не удалось загрузить файл. Пожалуйста, попробуйте еще раз. + + + Invited user: %1 + - - Failed to upload audio. Please try again. - Не удалось загрузить аудио. Пожалуйста, попробуйте еще раз. + + Failed to invite %1 to %2: %3 + - - Failed to upload video. Please try again. - Не удалось загрузить видео. Пожалуйста, попробуйте еще раз. + + Failed to kick %1 to %2: %3 + - + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + Failed to restore OLM account. Please login again. Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова. @@ -42,23 +65,43 @@ Не удалось восстановить сохраненные данные. Пожалуйста, войдите снова. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Не удалось настроить ключи шифрования. Ответ сервера:%1 %2. Пожалуйста, попробуйте позже. - + Please try to login again: %1 Повторите попытку входа: %1 - + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + Room creation failed: %1 Не удалось создать комнату: %1 - + + Room %1 created + + + + Failed to leave room: %1 Не удалось покинуть комнату: %1 @@ -66,7 +109,7 @@ CommunitiesListItem - + All rooms Все комнаты @@ -95,7 +138,7 @@ EditModal - + Apply Применить @@ -116,19 +159,11 @@ - FileItem + EncryptionIndicator - - Save File - Сохранить файл - - - - ImageItem - - - Save image - Сохранить изображение + + Encrypted + @@ -142,7 +177,7 @@ LoginPage - + Matrix ID Идентификатор Matrix @@ -168,6 +203,16 @@ + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + The required endpoints were not found. Possibly not a Matrix server. Необходимые конечные точки не найдены. Возможно, это не сервер Matrix. @@ -190,20 +235,61 @@ MemberList - + Room members Участники комнаты - - ESC - + + OK + + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + QuickSwitcher - + Search for a room... Поиск комнаты... @@ -211,7 +297,7 @@ RegisterPage - + Username Имя пользователя @@ -256,20 +342,36 @@ Неверное имя сервера + + ReplyPopup + + + Logout + Выйти + + + + RoomInfo + + + no version stored + + + RoomInfoListItem - + Leave room Покинуть комнату - + Accept Принять - + Decline Отказаться @@ -305,36 +407,36 @@ StatusIndicator - - Encrypted - Зашифровано + + Failed + - - Delivered - Доставлено - - - - Seen - Прочитано - - - + Sent - Отправлено + + + + + Received + + + + + Read + TextInputWidget - + Send a file Отправить файл - + Write a message... Написать сообщение... @@ -344,7 +446,12 @@ Отправить сообщение - + + Emoji + + + + Select a file Выберите файл @@ -360,30 +467,214 @@ - TimelineItem + TimelineModel - + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + + + + Message redaction failed: %1 - Ошибка редактирования сообщения: %1 + Ошибка редактирования сообщения: %1 + + + + Save image + Сохранить изображение + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + TimelineView - - Encryption is enabled - Шифрование включено + + Read receipts + Подтверждать прочтение + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + Закрыть TopRoomBar - + Room options Настройки комнаты - + + Mentions + + + + Invite users Пригласить пользователей @@ -406,7 +697,7 @@ TrayIcon - + Show Показать @@ -416,23 +707,10 @@ Выйти - - TypingDisplay - - - is typing - печатает - - - - are typing - печатают - - UserInfoWidget - + Logout Выйти @@ -440,37 +718,47 @@ UserSettingsPage - + Minimize to tray Сворачивать в системную панель - + Start in tray Запускать в системной панели - + Group's sidebar Боковая панель групп - + + Circular Avatars + + + + Typing notifications Сообщать о наборе сообщения - + Read receipts Подтверждать прочтение - + + Send messages as Markdown + + + + Desktop notifications Уведомления на рабочем столе - + Scale factor Масштаб @@ -480,12 +768,22 @@ Размер шрифта - + + Font Family + + + + + Emoji Font Famly + + + + Theme Тема - + Device ID ID устройства @@ -520,7 +818,7 @@ ГЛАВНОЕ - + Open Sessions File Открыть файл сеансов @@ -532,14 +830,14 @@ - + Error Ошибка - + File Password Или введите пароль? @@ -570,7 +868,7 @@ WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. Добро пожаловать в nheko, клиент протокола Matrix! @@ -590,6 +888,14 @@ ВХОД + + descriptiveTime + + + Yesterday + + + dialogs::CreateRoom @@ -693,7 +999,7 @@ dialogs::PreviewUploadOverlay - + Upload Загрузить @@ -733,7 +1039,7 @@ Media size: %2 dialogs::ReadReceipts - + Read receipts Подтверждать прочтение @@ -742,10 +1048,18 @@ Media size: %2 Close Закрыть + + + dialogs::ReceiptItem - - ESC - + + Today %1 + + + + + Yesterday %1 + @@ -765,18 +1079,23 @@ Media size: %2 Internal ID Внутренний ID + + + Room Version + + Notifications Уведомления - + Muted Приглушено - + Mentions only Только упоминания @@ -786,7 +1105,7 @@ Media size: %2 Все сообщения - + Room access Доступ к комнате @@ -833,7 +1152,7 @@ Media size: %2 - + %n member(s) %n участник @@ -842,17 +1161,12 @@ Media size: %2 - - ESC - - - - + Failed to enable encryption: %1 Не удалось включить шифрование: %1 - + Select an avatar Выберите аватар @@ -863,13 +1177,13 @@ Media size: %2 - The selected media is not an image - Выбранное медия не является изображением + The selected file is not an image + - Error while reading media: %1 - Ошибка при чтении медия: %1 + Error while reading file: %1 + @@ -886,7 +1200,7 @@ Media size: %2 Заблокировать пользователя в комнате - + Ignore messages from this user Игнорировать сообщения от этого пользователя @@ -896,19 +1210,161 @@ Media size: %2 Выгнать пользователя из комнаты - + Start a conversation Начать разговор - + Devices Устройства + + + emoji::Panel - - ESC - + + Smileys & People + + + + + Animals & Nature + + + + + Food & Drink + + + + + Activity + + + + + Travel & Places + + + + + Objects + + + + + Symbols + + + + + Flags + + + + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index 3ea4031c..536e39a2 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -1,38 +1,61 @@ - - AudioItem - - - Save File - 保存文件 - - ChatPage - - Failed to upload image. Please try again. - 上传图像失败。请重试。 + + Failed to invite user: %1 + - - Failed to upload file. Please try again. - 上传文件失败,请重试。 + + + Invited user: %1 + - - Failed to upload audio. Please try again. - 上传音频失败。请重试。 + + Failed to invite %1 to %2: %3 + - - Failed to upload video. Please try again. - 上传视频失败。请重试。 + + Failed to kick %1 to %2: %3 + - + + Kicked user: %1 + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + Failed to restore OLM account. Please login again. 恢复 OLM 账户失败。请重新登录。 @@ -42,54 +65,90 @@ 恢复保存的数据失败。请重新登录。 - - Failed to setup encryption keys. Server response: %s %d. Please try again later. - 建立加密密钥失败。 服务器返回:%s %d. 请稍后重试。 + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + - + Please try to login again: %1 请尝试再次登录:%1 - + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + Room creation failed: %1 创建聊天室失败:%1 - + + Room %1 created + + + + Failed to leave room: %1 离开聊天室失败:%1 - DateSeparator + CommunitiesListItem - - Today - 今天 + + All rooms + + + + + Favourite rooms + - Yesterday - 昨天 + Low priority rooms + + + + + + (tag) + + + + + (community) + EditModal - - APPLY - 应用 + + Apply + - - CANCEL - 取消 + + Cancel + 取消 - + Name 名称 @@ -100,25 +159,25 @@ - FileItem + EncryptionIndicator - - Save File - 保存文件 + + Encrypted + - ImageItem + InviteeItem - - Save image - 保存图像 + + Remove + LoginPage - + Matrix ID @@ -143,7 +202,17 @@ 登录 - + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + The required endpoints were not found. Possibly not a Matrix server. 没找到要求的终端。可能不是一个 Matrix 服务器。 @@ -166,20 +235,61 @@ MemberList - + Room members 聊天室成员 - - SHOW MORE - 显示更多 + + OK + + + + + MessageDelegate + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + Placeholder + + + unimplemented event: + QuickSwitcher - + Search for a room... 寻找一个聊天室... @@ -187,7 +297,7 @@ RegisterPage - + Username 用户名 @@ -207,7 +317,7 @@ 服务器 - + REGISTER 注册 @@ -232,20 +342,36 @@ 无效的服务器名 + + ReplyPopup + + + Logout + 登出 + + + + RoomInfo + + + no version stored + + + RoomInfoListItem - + Leave room 离开聊天室 - + Accept 接受 - + Decline 拒绝 @@ -253,7 +379,7 @@ SideBarActions - + User settings 用户设置 @@ -281,41 +407,41 @@ StatusIndicator - - Encrypted - 加密的 + + Failed + - - Delivered - 已送达 - - - - Seen - 已阅读 - - - + Sent - 已发送 + + + + + Received + + + + + Read + TextInputWidget - + Send a file 发送一个文件 - - + + Write a message... 写一条消息... - + Send a message 发送一条消息 @@ -325,7 +451,7 @@ - + Select a file 选择一个文件 @@ -341,30 +467,212 @@ - TimelineItem + TimelineModel - + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted + + + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1 + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet + + + + Message redaction failed: %1 - 删除消息失败:%1 + 删除消息失败:%1 + + + + Save image + 保存图像 + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1 + + + + + %1 redacted their knock. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouln't happen apart from state resets + + + + + %1 was banned. + + + + + %1 knocked. + + + + + TimelineRow + + + Reply + + + + + Options + TimelineView - - Encryption is enabled - 加密已启用 + + Read receipts + 阅读回执 + + + + Mark as read + + + + + View raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Close + TopRoomBar - + Room options 聊天室选项 - + + Mentions + + + + Invite users 邀请用户 @@ -387,7 +695,7 @@ TrayIcon - + Show 显示 @@ -397,23 +705,10 @@ 退出 - - TypingDisplay - - - is typing - 正在打字 - - - - are typing - 正在打字 - - UserInfoWidget - + Logout 登出 @@ -421,62 +716,82 @@ UserSettingsPage - + Minimize to tray 最小化至托盘 - + Start in tray 在托盘启动 - - Re-order rooms based on activity - 根据活动重排序聊天室 - - - + Group's sidebar 群组侧边栏 - + + Circular Avatars + + + + Typing notifications 打字通知 - + Read receipts 阅读回执 - + + Send messages as Markdown + + + + Desktop notifications 桌面通知 - - Scale factor (requires restart) - 缩放系数(需要重启) + + Scale factor + - + + Font size + + + + + Font Family + + + + + Emoji Font Famly + + + + Theme 主题 - + Device ID 设备 ID - + Device Fingerprint 设备指纹 - + Session Keys 会话密钥 @@ -501,48 +816,48 @@ 通用 - + Open Sessions File 打开会话文件 - + - + - + Error 错误 - - + + File Password 文件密码 - + Enter the passphrase to decrypt the file: 输入密码以解密文件: - - + + The password cannot be empty 密码不能为空 - + Enter passphrase to encrypt your session keys: 输入密码以加密你的会话密钥: - + File to save the exported session keys 保存导出的会话密钥的文件 @@ -550,7 +865,7 @@ WelcomePage - + Welcome to nheko! The desktop client for the Matrix protocol. 欢迎使用 nheko! Matrix 协议的桌面客户端。 @@ -560,25 +875,38 @@ 祝您使用愉快! - + REGISTER 注册 - + LOGIN 登录 + + descriptiveTime + + + Yesterday + + + dialogs::CreateRoom - - CANCEL - 取消 + + Create room + - + + Cancel + 取消 + + + Name 名称 @@ -611,9 +939,9 @@ dialogs::InviteUsers - - CANCEL - 取消 + + Cancel + 取消 @@ -624,12 +952,17 @@ dialogs::JoinRoom - - CANCEL - 取消 + + Join + - + + Cancel + 取消 + + + Room ID or alias 聊天室 ID 或别名 @@ -637,12 +970,12 @@ dialogs::LeaveRoom - - CANCEL - 取消 + + Cancel + 取消 - + Are you sure you want to leave? 你确定要离开吗? @@ -650,12 +983,12 @@ dialogs::Logout - - CANCEL - 取消 + + Cancel + 取消 - + Logout. Are you sure? 登出。确定吗? @@ -663,7 +996,7 @@ dialogs::PreviewUploadOverlay - + Upload 上传 @@ -673,7 +1006,7 @@ 取消 - + Media type: %1 Media size: %2 @@ -685,14 +1018,14 @@ Media size: %2 dialogs::ReCaptcha - - CONFIRM - 确定 + + Cancel + 取消 - - CANCEL - 取消 + + Confirm + @@ -703,15 +1036,33 @@ Media size: %2 dialogs::ReadReceipts - + Read receipts 阅读回执 + + + Close + + + + + dialogs::ReceiptItem + + + Today %1 + + + + + Yesterday %1 + + dialogs::RoomSettings - + Settings 设置 @@ -725,18 +1076,23 @@ Media size: %2 Internal ID 内部 ID + + + Room Version + + Notifications 通知 - + Muted 静默 - + Mentions only 只限提及 @@ -746,7 +1102,7 @@ Media size: %2 所有消息 - + Room access 聊天室访问 @@ -795,19 +1151,19 @@ Media size: %2 这是一个临时的测试端到端加密的方案。 - + %n member(s) %n 成员 - + Failed to enable encryption: %1 启用加密失败:%1 - + Select an avatar 选择一个头像 @@ -818,13 +1174,13 @@ Media size: %2 - The selected media is not an image - 选择的媒体不是一个图像 + The selected file is not an image + - Error while reading media: %1 - 读取媒体时失败:%1 + Error while reading file: %1 + @@ -836,12 +1192,12 @@ Media size: %2 dialogs::UserProfile - + Ban the user from the room 在这个聊天室封禁这个用户 - + Ignore messages from this user 忽略这个用户的消息 @@ -851,12 +1207,12 @@ Media size: %2 把这个用户踢出聊天室 - + Start a conversation 开始一个聊天 - + Devices 设备 @@ -864,7 +1220,7 @@ Media size: %2 emoji::Panel - + Smileys & People 笑脸和人 Smileys & People @@ -912,4 +1268,108 @@ Media size: %2 Flags + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + diff --git a/resources/login.png b/resources/login.png index e65084ef..d098a62a 100644 Binary files a/resources/login.png and b/resources/login.png differ diff --git a/resources/login@2x.png b/resources/login@2x.png index 4f89112f..9cbe3c9f 100644 Binary files a/resources/login@2x.png and b/resources/login@2x.png differ diff --git a/resources/nheko-1024.png b/resources/nheko-1024.png index 45b19a62..8a59d5f6 100644 Binary files a/resources/nheko-1024.png and b/resources/nheko-1024.png differ diff --git a/resources/nheko-128.png b/resources/nheko-128.png index e65084ef..d098a62a 100644 Binary files a/resources/nheko-128.png and b/resources/nheko-128.png differ diff --git a/resources/nheko-16.png b/resources/nheko-16.png index deb4449d..7114e060 100644 Binary files a/resources/nheko-16.png and b/resources/nheko-16.png differ diff --git a/resources/nheko-256.png b/resources/nheko-256.png index 4f89112f..9cbe3c9f 100644 Binary files a/resources/nheko-256.png and b/resources/nheko-256.png differ diff --git a/resources/nheko-32.png b/resources/nheko-32.png index ec582489..5fefc6b5 100644 Binary files a/resources/nheko-32.png and b/resources/nheko-32.png differ diff --git a/resources/nheko-48.png b/resources/nheko-48.png index e5aab6ac..726d0356 100644 Binary files a/resources/nheko-48.png and b/resources/nheko-48.png differ diff --git a/resources/nheko-512.png b/resources/nheko-512.png index 3c39b0be..29c3a607 100644 Binary files a/resources/nheko-512.png and b/resources/nheko-512.png differ diff --git a/resources/nheko-64.png b/resources/nheko-64.png index 768921c9..1b5c9eb3 100644 Binary files a/resources/nheko-64.png and b/resources/nheko-64.png differ diff --git a/resources/nheko.png b/resources/nheko.png index 3c39b0be..ae7398a6 100644 Binary files a/resources/nheko.png and b/resources/nheko.png differ diff --git a/resources/nheko.svg b/resources/nheko.svg new file mode 100644 index 00000000..ce3ec406 --- /dev/null +++ b/resources/nheko.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml new file mode 100644 index 00000000..0a53eac9 --- /dev/null +++ b/resources/qml/Avatar.qml @@ -0,0 +1,53 @@ +import QtQuick 2.6 +import QtGraphicalEffects 1.0 +import Qt.labs.settings 1.0 + +Rectangle { + id: avatar + width: 48 + height: 48 + radius: settings.avatar_circles ? height/2 : 3 + + Settings { + id: settings + category: "user" + property bool avatar_circles: true + } + + property alias url: img.source + property string displayName + + Text { + anchors.fill: parent + text: chat.model.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0))) + textFormat: Text.RichText + color: colors.text + font.pixelSize: avatar.height/2 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + visible: img.status != Image.Ready + } + + Image { + id: img + anchors.fill: parent + asynchronous: true + fillMode: Image.PreserveAspectCrop + mipmap: true + smooth: false + + sourceSize.width: avatar.width + sourceSize.height: avatar.height + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + anchors.fill: parent + width: avatar.width + height: avatar.height + radius: settings.avatar_circles ? height/2 : 3 + } + } + } + color: colors.base +} diff --git a/resources/qml/EncryptionIndicator.qml b/resources/qml/EncryptionIndicator.qml new file mode 100644 index 00000000..00fe2ee4 --- /dev/null +++ b/resources/qml/EncryptionIndicator.qml @@ -0,0 +1,26 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import im.nheko 1.0 + +Rectangle { + id: indicator + color: "transparent" + width: 16 + height: 16 + + ToolTip.visible: ma.containsMouse && indicator.visible + ToolTip.text: qsTr("Encrypted") + + MouseArea{ + id: ma + anchors.fill: parent + hoverEnabled: true + } + + Image { + id: stateImg + anchors.fill: parent + source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText + } +} + diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml new file mode 100644 index 00000000..dc576e18 --- /dev/null +++ b/resources/qml/ImageButton.qml @@ -0,0 +1,29 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.3 + +Button { + property string image: undefined + + id: button + + flat: true + + // disable background, because we don't want a border on hover + background: Item { + } + + Image { + id: buttonImg + // Workaround, can't get icon.source working for now... + anchors.fill: parent + source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText) + } + + MouseArea + { + id: mouseArea + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: Qt.PointingHandCursor + } +} diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml new file mode 100644 index 00000000..9a4f7348 --- /dev/null +++ b/resources/qml/MatrixText.qml @@ -0,0 +1,32 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.3 + +TextEdit { + textFormat: TextEdit.RichText + readOnly: true + wrapMode: Text.Wrap + selectByMouse: true + color: colors.text + + onLinkActivated: { + if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]) + else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) timelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]) + else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) { + var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link) + timelineManager.setHistoryView(match[1]) + chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain) + } + else Qt.openUrlExternally(link) + } + MouseArea + { + id: ma + anchors.fill: parent + propagateComposedEvents: true + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + + ToolTip.visible: hoveredLink + ToolTip.text: hoveredLink +} diff --git a/resources/qml/StatusIndicator.qml b/resources/qml/StatusIndicator.qml new file mode 100644 index 00000000..ec82ed49 --- /dev/null +++ b/resources/qml/StatusIndicator.qml @@ -0,0 +1,39 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import im.nheko 1.0 + +Rectangle { + id: indicator + property int state: 0 + color: "transparent" + width: 16 + height: 16 + + ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty + ToolTip.text: switch (state) { + case MtxEvent.Failed: return qsTr("Failed") + case MtxEvent.Sent: return qsTr("Sent") + case MtxEvent.Received: return qsTr("Received") + case MtxEvent.Read: return qsTr("Read") + default: return "" + } + MouseArea{ + id: ma + anchors.fill: parent + hoverEnabled: true + } + + Image { + id: stateImg + // Workaround, can't get icon.source working for now... + anchors.fill: parent + source: switch (indicator.state) { + case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText + case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText + case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText + case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText + default: return "" + } + } +} + diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml new file mode 100644 index 00000000..2984844f --- /dev/null +++ b/resources/qml/TimelineRow.qml @@ -0,0 +1,134 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 + +import im.nheko 1.0 + +import "./delegates" + +MouseArea { + id: rowArea + + anchors.left: parent.left + anchors.right: parent.right + height: row.height + + hoverEnabled: true + preventStealing: true + propagateComposedEvents: true + acceptedButtons: Qt.NoButton + + property bool showButtons: false + + Timer { + running: rowArea.containsMouse + interval: 150 + onTriggered: rowArea.state = "showButtons" + } + + states: [ + State { + name: "hideButtons" + when: !rowArea.containsMouse + PropertyChanges { target: rowArea; showButtons: false; } + }, + State { + name: "showButtons" + PropertyChanges { target: rowArea; showButtons: true; } + } + ] + + RowLayout { + id: row + + anchors.leftMargin: avatarSize + 4 + anchors.left: parent.left + anchors.right: parent.right + + + Column { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + spacing: 4 + + // fancy reply, if this is a reply + Reply { + visible: model.replyTo + modelData: chat.model.getDump(model.replyTo) + userColor: timelineManager.userColor(modelData.userId, colors.window) + } + + // actual message content + MessageDelegate { + id: contentItem + + width: parent.width + + modelData: model + } + } + + ImageButton { + visible: rowArea.showButtons + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + width: 16 + id: replyButton + hoverEnabled: true + + + image: ":/icons/icons/ui/mail-reply.png" + + ToolTip.visible: hovered + ToolTip.text: qsTr("Reply") + + onClicked: chat.model.replyAction(model.id) + } + ImageButton { + visible: rowArea.showButtons + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + width: 16 + id: optionsButton + hoverEnabled: true + + image: ":/icons/icons/ui/vertical-ellipsis.png" + + ToolTip.visible: hovered + ToolTip.text: qsTr("Options") + + onClicked: messageContextMenu.show(model.id, model.type, optionsButton) + + } + + StatusIndicator { + state: model.state + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + width: 16 + } + + EncryptionIndicator { + visible: model.isEncrypted + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + width: 16 + } + + Text { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + text: model.timestamp.toLocaleTimeString("HH:mm") + color: inactiveColors.text + + MouseArea{ + id: ma + anchors.fill: parent + hoverEnabled: true + } + + ToolTip.visible: ma.containsMouse + ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) + } + } +} diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml new file mode 100644 index 00000000..46cf484b --- /dev/null +++ b/resources/qml/TimelineView.qml @@ -0,0 +1,309 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 +import QtQuick.Window 2.2 + +import im.nheko 1.0 + +import "./delegates" + +Item { + property var colors: currentActivePalette + property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled } + property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive + property int avatarSize: 40 + + Menu { + id: messageContextMenu + palette: colors + modal: true + + function show(eventId_, eventType_, showAt) { + eventId = eventId_ + eventType = eventType_ + popup(showAt) + } + + property string eventId + property int eventType + + MenuItem { + text: qsTr("Read receipts") + onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId) + } + MenuItem { + text: qsTr("Mark as read") + } + MenuItem { + text: qsTr("View raw message") + onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId) + } + MenuItem { + text: qsTr("Redact message") + onTriggered: chat.model.redactEvent(messageContextMenu.eventId) + } + MenuItem { + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker + text: qsTr("Save as") + onTriggered: timelineManager.timeline.saveMedia(messageContextMenu.eventId) + } + } + + id: timelineRoot + + Rectangle { + anchors.fill: parent + color: colors.window + + Text { + visible: !timelineManager.timeline && !timelineManager.isInitialSync + anchors.centerIn: parent + text: qsTr("No room open") + font.pointSize: 24 + color: colors.windowText + } + + BusyIndicator { + anchors.centerIn: parent + running: timelineManager.isInitialSync + height: 200 + width: 200 + z: 3 + } + + ListView { + id: chat + + visible: timelineManager.timeline != null + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: chatFooter.top + + anchors.leftMargin: 4 + anchors.rightMargin: scrollbar.width + + model: timelineManager.timeline + + boundsBehavior: Flickable.StopAtBounds + pixelAligned: true + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + propagateComposedEvents: true + z: -1 + onWheel: { + if (wheel.angleDelta != 0) { + chat.contentY = chat.contentY - wheel.angleDelta.y + wheel.accepted = true + chat.returnToBounds() + } + } + } + + Shortcut { + sequence: StandardKey.MoveToPreviousPage + onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); } + } + Shortcut { + sequence: StandardKey.MoveToNextPage + onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); } + } + + ScrollBar.vertical: ScrollBar { + id: scrollbar + parent: chat.parent + anchors.top: chat.top + anchors.left: chat.right + anchors.bottom: chat.bottom + } + + spacing: 4 + verticalLayoutDirection: ListView.BottomToTop + + onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom + + delegate: Rectangle { + // This would normally be previousSection, but our model's order is inverted. + property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1 + + id: wrapper + property Item section + width: chat.width + height: section ? section.height + timelinerow.height : timelinerow.height + color: "transparent" + + TimelineRow { + id: timelinerow + y: section ? section.y + section.height : 0 + } + + onSectionBoundaryChanged: { + if (sectionBoundary) { + var properties = { + 'modelData': model.dump, + 'section': ListView.section, + 'nextSection': ListView.nextSection + } + section = sectionHeader.createObject(wrapper, properties) + } else { + section.destroy() + section = null + } + } + + Binding { + target: chat.model + property: "currentIndex" + when: y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height + value: index + delayed: true + } + + } + + section { + property: "section" + } + Component { + id: sectionHeader + Column { + property var modelData + property string section + property string nextSection + + topPadding: 4 + bottomPadding: 4 + spacing: 8 + + visible: !!modelData + + width: parent.width + height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 + + Label { + id: dateBubble + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined + visible: section.includes(" ") + text: chat.model.formatDateSeparator(modelData.timestamp) + color: colors.windowText + + height: contentHeight * 1.2 + width: contentWidth * 1.2 + horizontalAlignment: Text.AlignHCenter + background: Rectangle { + radius: parent.height / 2 + color: colors.base + } + } + Row { + height: userName.height + spacing: 4 + Avatar { + width: avatarSize + height: avatarSize + url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") + displayName: modelData.userName + + MouseArea { + anchors.fill: parent + onClicked: chat.model.openUserProfile(modelData.userId) + cursorShape: Qt.PointingHandCursor + } + } + + Text { + id: userName + text: chat.model.escapeEmoji(modelData.userName) + color: timelineManager.userColor(modelData.userId, colors.window) + textFormat: Text.RichText + + MouseArea { + anchors.fill: parent + onClicked: chat.model.openUserProfile(section.split(" ")[0]) + cursorShape: Qt.PointingHandCursor + } + } + } + } + } + + } + + Rectangle { + id: chatFooter + + height: Math.max(16, footerContent.height) + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + z: 3 + + color: "transparent" + + Column { + id: footerContent + anchors.left: parent.left + anchors.right: parent.right + + Text { + id: typingDisplay + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : "" + textFormat: Text.RichText + color: colors.windowText + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + + id: replyPopup + + visible: timelineManager.replyingEvent && chat.model + // Height of child, plus margins, plus border + height: replyPreview.height + 10 + color: colors.base + + + Reply { + id: replyPreview + + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.right: closeReplyButton.left + anchors.rightMargin: 20 + anchors.bottom: parent.bottom + + modelData: chat.model ? chat.model.getDump(timelineManager.replyingEvent) : {} + userColor: timelineManager.userColor(modelData.userId, colors.window) + } + + ImageButton { + id: closeReplyButton + + anchors.right: parent.right + anchors.rightMargin: 15 + anchors.top: replyPreview.top + hoverEnabled: true + width: 16 + height: 16 + + image: ":/icons/icons/ui/remove-symbol.png" + ToolTip.visible: closeReplyButton.hovered + ToolTip.text: qsTr("Close") + + onClicked: timelineManager.closeReply() + } + } + } + } + } +} diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml new file mode 100644 index 00000000..2fe0a490 --- /dev/null +++ b/resources/qml/delegates/FileMessage.qml @@ -0,0 +1,57 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.2 + +Rectangle { + radius: 10 + color: colors.base + height: row.height + 24 + width: parent ? parent.width : undefined + + RowLayout { + id: row + + anchors.centerIn: parent + width: parent.width - 24 + + spacing: 15 + + Rectangle { + id: button + color: colors.light + radius: 22 + height: 44 + width: 44 + Image { + id: img + anchors.centerIn: parent + + source: "qrc:/icons/icons/ui/arrow-pointing-down.png" + fillMode: Image.Pad + + } + MouseArea { + anchors.fill: parent + onClicked: timelineManager.timeline.saveMedia(model.data.id) + cursorShape: Qt.PointingHandCursor + } + } + ColumnLayout { + id: col + + Text { + Layout.fillWidth: true + text: model.data.body + textFormat: Text.PlainText + elide: Text.ElideRight + color: colors.text + } + Text { + Layout.fillWidth: true + text: model.data.filesize + textFormat: Text.PlainText + elide: Text.ElideRight + color: colors.text + } + } + } +} diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml new file mode 100644 index 00000000..cb05021d --- /dev/null +++ b/resources/qml/delegates/ImageMessage.qml @@ -0,0 +1,28 @@ +import QtQuick 2.6 + +import im.nheko 1.0 + +Item { + property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width) + property double tempHeight: tempWidth * model.data.proportionalHeight + + property bool tooHigh: tempHeight > timelineRoot.height / 2 + + height: tooHigh ? timelineRoot.height / 2 : tempHeight + width: tooHigh ? (timelineRoot.height / 2) / model.data.proportionalHeight : tempWidth + + Image { + id: img + anchors.fill: parent + + source: model.data.url.replace("mxc://", "image://MxcImage/") + asynchronous: true + fillMode: Image.PreserveAspectFit + + MouseArea { + enabled: model.data.type == MtxEvent.ImageMessage + anchors.fill: parent + onClicked: timelineManager.openImageOverlay(model.data.url, model.data.id) + } + } +} diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml new file mode 100644 index 00000000..ab633087 --- /dev/null +++ b/resources/qml/delegates/MessageDelegate.qml @@ -0,0 +1,94 @@ +import QtQuick 2.6 +import im.nheko 1.0 + +Item { + // Workaround to have an assignable global property + Item { + id: model + property var data; + } + + property alias modelData: model.data + + height: chooser.childrenRect.height + + DelegateChooser { + id: chooser + //role: "type" //< not supported in our custom implementation, have to use roleValue + roleValue: model.data.type + anchors.fill: parent + + DelegateChoice { + roleValue: MtxEvent.UnknownMessage + Placeholder { text: "Unretrieved event" } + } + DelegateChoice { + roleValue: MtxEvent.TextMessage + TextMessage {} + } + DelegateChoice { + roleValue: MtxEvent.NoticeMessage + NoticeMessage {} + } + DelegateChoice { + roleValue: MtxEvent.EmoteMessage + NoticeMessage { + formatted: chat.model.escapeEmoji(modelData.userName) + " " + model.data.formattedBody + color: timelineManager.userColor(modelData.userId, colors.window) + } + } + DelegateChoice { + roleValue: MtxEvent.ImageMessage + ImageMessage {} + } + DelegateChoice { + roleValue: MtxEvent.Sticker + ImageMessage {} + } + DelegateChoice { + roleValue: MtxEvent.FileMessage + FileMessage {} + } + DelegateChoice { + roleValue: MtxEvent.VideoMessage + PlayableMediaMessage {} + } + DelegateChoice { + roleValue: MtxEvent.AudioMessage + PlayableMediaMessage {} + } + DelegateChoice { + roleValue: MtxEvent.Redacted + Pill { + text: qsTr("redacted") + } + } + DelegateChoice { + roleValue: MtxEvent.Encryption + Pill { + text: qsTr("Encryption enabled") + } + } + DelegateChoice { + roleValue: MtxEvent.Name + NoticeMessage { + text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name") + } + } + DelegateChoice { + roleValue: MtxEvent.Topic + NoticeMessage { + text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic") + } + } + DelegateChoice { + roleValue: MtxEvent.Member + NoticeMessage { + text: timelineManager.timeline.formatMemberEvent(model.data.id); + } + } + DelegateChoice { + Placeholder {} + } + } +} diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml new file mode 100644 index 00000000..12664fb5 --- /dev/null +++ b/resources/qml/delegates/NoticeMessage.qml @@ -0,0 +1,4 @@ +TextMessage { + font.italic: true + color: inactiveColors.text +} diff --git a/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml new file mode 100644 index 00000000..b19d9a54 --- /dev/null +++ b/resources/qml/delegates/Pill.qml @@ -0,0 +1,14 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 + +Label { + color: inactiveColors.text + horizontalAlignment: Text.AlignHCenter + + height: contentHeight * 1.2 + width: contentWidth * 1.2 + background: Rectangle { + radius: parent.height / 2 + color: colors.base + } +} diff --git a/resources/qml/delegates/Placeholder.qml b/resources/qml/delegates/Placeholder.qml new file mode 100644 index 00000000..26de2067 --- /dev/null +++ b/resources/qml/delegates/Placeholder.qml @@ -0,0 +1,7 @@ +import ".." + +MatrixText { + text: qsTr("unimplemented event: ") + model.data.typeString + width: parent ? parent.width : undefined + color: inactiveColors.text +} diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml new file mode 100644 index 00000000..a4096864 --- /dev/null +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -0,0 +1,167 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 +import QtMultimedia 5.6 + +import im.nheko 1.0 + +Rectangle { + id: bg + radius: 10 + color: colors.base + height: content.height + 24 + width: parent ? parent.width : undefined + + Column { + id: content + width: parent.width - 24 + anchors.centerIn: parent + + Rectangle { + id: videoContainer + visible: model.data.type == MtxEvent.VideoMessage + width: Math.min(parent.width, model.data.width ? model.data.width : 400) // some media has 0 as size... + height: width*model.data.proportionalHeight + Image { + anchors.fill: parent + source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/") + asynchronous: true + fillMode: Image.PreserveAspectFit + + VideoOutput { + anchors.fill: parent + fillMode: VideoOutput.PreserveAspectFit + source: media + } + } + } + + RowLayout { + width: parent.width + Text { + id: positionText + text: "--:--:--" + color: colors.text + } + Slider { + Layout.fillWidth: true + id: progress + value: media.position + from: 0 + to: media.duration + + onMoved: media.seek(value) + //indeterminate: true + function updatePositionTexts() { + function formatTime(date) { + var hh = date.getUTCHours(); + var mm = date.getUTCMinutes(); + var ss = date.getSeconds(); + if (hh < 10) {hh = "0"+hh;} + if (mm < 10) {mm = "0"+mm;} + if (ss < 10) {ss = "0"+ss;} + return hh+":"+mm+":"+ss; + } + positionText.text = formatTime(new Date(media.position)) + durationText.text = formatTime(new Date(media.duration)) + } + onValueChanged: updatePositionTexts() + + palette: colors + } + Text { + id: durationText + text: "--:--:--" + color: colors.text + } + } + + RowLayout { + width: parent.width + + spacing: 15 + + Rectangle { + id: button + color: colors.window + radius: 22 + height: 44 + width: 44 + Image { + id: img + anchors.centerIn: parent + z: 3 + + source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+colors.text + fillMode: Image.Pad + + } + MouseArea { + anchors.fill: parent + onClicked: { + switch (button.state) { + case "": timelineManager.timeline.cacheMedia(model.data.id); break; + case "stopped": + media.play(); console.log("play"); + button.state = "playing" + break + case "playing": + media.pause(); console.log("pause"); + button.state = "stopped" + break + } + } + cursorShape: Qt.PointingHandCursor + } + MediaPlayer { + id: media + onError: console.log(errorString) + onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts() + onStopped: button.state = "stopped" + } + + Connections { + target: timelineManager.timeline + onMediaCached: { + if (mxcUrl == model.data.url) { + media.source = "file://" + cacheUrl + button.state = "stopped" + console.log("media loaded: " + mxcUrl + " at " + cacheUrl) + } + console.log("media cached: " + mxcUrl + " at " + cacheUrl) + } + } + + states: [ + State { + name: "stopped" + PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/play-sign.png?"+colors.text } + }, + State { + name: "playing" + PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+colors.text } + } + ] + } + ColumnLayout { + id: col + + Text { + Layout.fillWidth: true + text: model.data.body + textFormat: Text.PlainText + elide: Text.ElideRight + color: colors.text + } + Text { + Layout.fillWidth: true + text: model.data.filesize + textFormat: Text.PlainText + elide: Text.ElideRight + color: colors.text + } + } + } + } +} + diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml new file mode 100644 index 00000000..06804328 --- /dev/null +++ b/resources/qml/delegates/Reply.qml @@ -0,0 +1,58 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 + +Rectangle { + id: replyComponent + + property alias modelData: reply.modelData + property color userColor: "red" + + width: parent.width + height: replyContainer.height + + MouseArea { + anchors.fill: parent + preventStealing: true + onClicked: chat.positionViewAtIndex(chat.model.idToIndex(timelineManager.replyingEvent), ListView.Contain) + cursorShape: Qt.PointingHandCursor + } + + Rectangle { + id: colorLine + + anchors.top: replyContainer.top + anchors.bottom: replyContainer.bottom + width: 4 + + color: timelineManager.userColor(reply.modelData.userId, colors.window) + } + + Column { + id: replyContainer + anchors.left: colorLine.right + anchors.leftMargin: 4 + width: parent.width - 8 + + Text { + id: userName + text: chat.model ? chat.model.escapeEmoji(reply.modelData.userName) : "" + color: replyComponent.userColor + textFormat: Text.RichText + + MouseArea { + anchors.fill: parent + onClicked: chat.model.openUserProfile(reply.modelData.userId) + cursorShape: Qt.PointingHandCursor + } + } + + MessageDelegate { + id: reply + width: parent.width + } + } + + color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2) +} diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml new file mode 100644 index 00000000..7e4b1f29 --- /dev/null +++ b/resources/qml/delegates/TextMessage.qml @@ -0,0 +1,7 @@ +import ".." + +MatrixText { + property string formatted: model.data.formattedBody + text: "" + formatted.replace("
", "
")
+	width: parent ? parent.width : undefined
+}
diff --git a/resources/register.png b/resources/register.png
index e65084ef..d098a62a 100644
Binary files a/resources/register.png and b/resources/register.png differ
diff --git a/resources/register@2x.png b/resources/register@2x.png
index 4f89112f..9cbe3c9f 100644
Binary files a/resources/register@2x.png and b/resources/register@2x.png differ
diff --git a/resources/res.qrc b/resources/res.qrc
index cef55773..7080fdd6 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -1,5 +1,6 @@
 
     
+        icons/ui/at-solid.svg
         icons/ui/volume-off-indicator.png
         icons/ui/volume-off-indicator@2x.png
         icons/ui/black-bubble-speech.png
@@ -62,6 +63,8 @@
 
         icons/ui/edit.png
         icons/ui/edit@2x.png
+    
+        icons/ui/mail-reply.png
 
         icons/emoji-categories/people.png
         icons/emoji-categories/people@2x.png
@@ -82,6 +85,7 @@
     
     
         nheko.png
+        nheko.svg
 
         splash.png
         splash@2x.png
@@ -99,16 +103,27 @@
         nheko-32.png
         nheko-16.png
     
-    
-        fonts/OpenSans/OpenSans-Regular.ttf
-        fonts/OpenSans/OpenSans-Italic.ttf
-        fonts/OpenSans/OpenSans-Bold.ttf
-        fonts/OpenSans/OpenSans-Semibold.ttf
-        fonts/EmojiOne/emojione-android.ttf
-    
     
         styles/system.qss
         styles/nheko.qss
         styles/nheko-dark.qss
     
+    
+        qml/TimelineView.qml
+        qml/Avatar.qml
+        qml/ImageButton.qml
+        qml/MatrixText.qml
+        qml/StatusIndicator.qml
+        qml/EncryptionIndicator.qml
+        qml/TimelineRow.qml
+        qml/delegates/MessageDelegate.qml
+        qml/delegates/TextMessage.qml
+        qml/delegates/NoticeMessage.qml
+        qml/delegates/ImageMessage.qml
+        qml/delegates/PlayableMediaMessage.qml
+        qml/delegates/FileMessage.qml
+        qml/delegates/Pill.qml
+        qml/delegates/Placeholder.qml
+        qml/delegates/Reply.qml
+    
 
diff --git a/resources/splash.png b/resources/splash.png
index 4f89112f..9cbe3c9f 100644
Binary files a/resources/splash.png and b/resources/splash.png differ
diff --git a/resources/splash@2x.png b/resources/splash@2x.png
index 3c39b0be..29c3a607 100644
Binary files a/resources/splash@2x.png and b/resources/splash@2x.png differ
diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss
index 5567f32c..4efb6f30 100644
--- a/resources/styles/nheko-dark.qss
+++ b/resources/styles/nheko-dark.qss
@@ -3,13 +3,75 @@ QLabel {
     color: #caccd1;
 }
 
-TimelineItem {
-    qproperty-backgroundColor: #202228;
+TextLabel::a {
+    color: #38a3d8;
+}
+
+QuickSwitcher,
+ReplyPopup,
+SuggestionsPopup,
+UserSettingsPage,
+#scroll_widget,
+#UserSettingScrollWidget {
+    background-color: #202228;
 }
 
 #chatPage,
-#chatPage > * {
+#chatPage > *,
+CommunitiesList,
+CommunitiesList > *,
+RoomList,
+RoomList > *,
+TimelineView,
+TimelineView > *,
+UserMentionsWidget,
+UserMentionsWidget > * {
+    background-color: #2d3139;
+    border: none;
+}
+
+QLineEdit,
+QListWidget,
+WelcomePage,
+LoginPage,
+RegisterPage,
+EditModal,
+emoji--Panel,
+emoji--Panel > *,
+dialogs--Logout,
+dialogs--ReCaptcha,
+dialogs--LeaveRoom,
+dialogs--CreateRoom,
+dialogs--RoomSettings,
+dialogs--InviteUsers,
+dialogs--ReadReceipts,
+dialogs--JoinRoom,
+dialogs--MemberList,
+dialogs--PreviewUploadOverlay,
+dialogs--UserProfile,
+dialogs--CreateRoom > QLineEdit,
+dialogs--InviteUsers > QLineEdit,
+dialogs--JoinRoom > QLineEdit {
     background-color: #202228;
+    color: #caccd1;
+}
+
+emoji--Panel QWidget { border: none; }
+emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
+emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
+
+emoji--Category,
+emoji--Category > * {
+    background-color: #2d3139;
+    color: #727274;
+}
+
+emoji--Category QLabel {
+    margin: 20px 0 20px 8px;
+}
+
+TimelineItem {
+    qproperty-backgroundColor: #202228;
 }
 
 #sideBar {
@@ -18,18 +80,9 @@ TimelineItem {
     border-left: 1px solid #202228;
 }
 
-TimelineView,
-TimelineView > * {
-    background-color: #202228;
-    border: none;
-}
-
-#scroll_widget {
-    background-color: #202228;
-}
-
-QuickSwitcher {
-    background-color: #202228;
+UserMentionsWidget > TimelineItem {
+    qproperty-backgroundColor: #202228;
+    qproperty-hoverColor: rgba(45, 49, 57, 120);
 }
 
 InfoMessage {
@@ -37,21 +90,11 @@ InfoMessage {
     qproperty-boxColor: rgba(45, 49, 57, 120);
 }
 
-SuggestionsPopup {
-    background-color: #202228;
-}
-
 PopupItem {
     background-color: #202228;
     qproperty-hoverColor: rgba(45, 49, 57, 120);
 }
 
-RoomList,
-RoomList > * {
-    background-color: #2d3139;
-    border: none;
-}
-
 TypingDisplay {
     qproperty-textColor: #caccd1;
     qproperty-backgroundColor: #202228;
@@ -61,35 +104,26 @@ TypingDisplay {
     background-color: #2d3139;
 }
 
-CommunitiesList,
-CommunitiesList > * {
-    background-color: #2d3139;
-}
-
 FlatButton {
     qproperty-foregroundColor: #727274;
     qproperty-backgroundColor: #333;
     qproperty-disabledForegroundColor: #222;
 }
 
+AudioItem,
 FileItem {
     qproperty-textColor: #caccd1;
     qproperty-backgroundColor: #2d3139;
     qproperty-iconColor: #caccd1;
 }
 
-AudioItem {
-    qproperty-textColor: #caccd1;
-    qproperty-backgroundColor: #2d3139;
-    qproperty-iconColor: #caccd1;
-}
-
 RaisedButton {
     qproperty-foregroundColor: #caccd1;
     qproperty-backgroundColor: #333;
 }
 
-RoomInfoListItem {
+RoomInfoListItem,
+UserMentionsWidget {
     qproperty-mentionedColor: #a82353;
     qproperty-highlightedBackgroundColor: #4d84c7;
     qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
@@ -111,13 +145,15 @@ RoomInfoListItem {
     qproperty-highlightedTimestampColor: #e7e7e9;
     qproperty-hoverTimestampColor: #f4f5f8;
 
-    qproperty-avatarBgColor: #202228;
-    qproperty-avatarFgColor: white;
-
     qproperty-bubbleFgColor: white;
     qproperty-bubbleBgColor: #4d84c7;
 }
 
+RoomInfoListItem > Avatar {
+    qproperty-backgroundColor: #202228;
+    qproperty-textColor: white;
+}
+
 CommunitiesListItem {
     qproperty-highlightedBackgroundColor: #4d84c7;
     qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
@@ -141,12 +177,12 @@ UserInfoWidget {
     border-bottom: 1px solid #202228;
 }
 
-UserSettingsPage {
-    background-color: #202228;
+#UserSettingScrollWidget > QComboBox {
+    color: #202228;
 }
 
-#UserSettingScrollWidget {
-    background-color: #202228;
+#UserSettingScrollWidget > QComboBox {
+    color: #202228;
 }
 
 Avatar {
@@ -154,55 +190,17 @@ Avatar {
     qproperty-backgroundColor: #2d3139;
 }
 
-#displayNameLabel {
-    color: #f2f2f2;
-}
-
+#displayNameLabel,
 #userIdLabel {
     color: #f2f2f2;
 }
 
-dialogs--Logout,
-dialogs--ReCaptcha,
-dialogs--LeaveRoom,
-dialogs--CreateRoom,
-dialogs--RoomSettings,
-dialogs--InviteUsers,
-dialogs--ReadReceipts,
-dialogs--JoinRoom,
-dialogs--MemberList,
-dialogs--PreviewUploadOverlay,
-dialogs--UserProfile,
-dialogs--CreateRoom > QLineEdit,
-dialogs--InviteUsers > QLineEdit,
-EditModal,
-dialogs--JoinRoom > QLineEdit {
-    background-color: #202228;
-    color: #caccd1;
-}
-
 TopSection {
     qproperty-textColor: #caccd1;
 }
 
-QListWidget,
-WelcomePage,
-LoginPage,
-RegisterPage {
-    background-color: #202228;
-    color: #caccd1;
-}
-
-emoji--Panel,
-emoji--Panel > * {
-    background-color: #202228;
-    color: #caccd1;
-}
-
-emoji--Category,
-emoji--Category > * {
-    background-color: #2d3139;
-    color: #caccd1;
+emoji--Category {
+    qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
 }
 
 FloatingButton {
@@ -221,23 +219,14 @@ ScrollBar {
     qproperty-backgroundColor: #202228;
 }
 
-SideBarActions {
+SideBarActions,
+TopRoomBar
+{
     border: none;
     border-top: 1px solid #202228;
     background-color: #2d3139;
 }
 
-TopRoomBar {
-    border: none;
-    border-bottom: 1px solid #202228;
-    background-color: #2d3139;
-}
-
-QLineEdit {
-    background-color: #202228;
-    color: #caccd1;
-}
-
 TextInputWidget {
     border: none;
     border-top: 1px solid #2d3139;
@@ -261,3 +250,5 @@ SnackBar {
     qproperty-textColor: #caccd1;
     qproperty-bgColor: #202228;
 }
+
+QSplitter::handle { image: none; }
diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss
index 58e83c22..4c59bad1 100644
--- a/resources/styles/nheko.qss
+++ b/resources/styles/nheko.qss
@@ -3,13 +3,38 @@ QLabel {
     color: #333;
 }
 
-TimelineItem {
-    qproperty-backgroundColor: white;
+TextLabel::a {
+    color: #0077b5;
+}
+
+
+QuickSwitcher,
+ReplyPopup,
+SuggestionsPopup,
+UserSettingsPage,
+#scroll_widget,
+#UserSettingScrollWidget {
+    background-color: white;
 }
 
 #chatPage,
-#chatPage > * {
+#chatPage > *,
+CommunitiesList,
+CommunitiesList > *,
+RoomList,
+RoomList > *,
+TimelineView,
+TimelineView > *,
+UserMentionsWidget,
+UserMentionsWidget > *,
+TimelineView,
+TimelineView > * {
     background-color: white;
+    border: none;
+}
+
+TimelineItem {
+    qproperty-backgroundColor: white;
 }
 
 #sideBar {
@@ -18,18 +43,9 @@ TimelineItem {
     border-left: 1px solid #dee1f3;
 }
 
-TimelineView,
-TimelineView > * {
-    background-color: white;
-    border: none;
-}
-
-#scroll_widget {
-    background-color: white;
-}
-
-QuickSwitcher {
-    background-color: white;
+UserMentionsWidget > TimelineItem {
+    qproperty-backgroundColor: white;
+    qproperty-hoverColor: rgba(192, 193, 195, 120);
 }
 
 InfoMessage {
@@ -42,17 +58,15 @@ TypingDisplay {
     qproperty-backgroundColor: white;
 }
 
-SuggestionsPopup {
-    background-color: white;
-}
-
 PopupItem {
     background-color: white;
     qproperty-hoverColor: rgba(192, 193, 195, 120);
 }
 
 RoomList,
-RoomList > * {
+RoomList > *,
+CommunitiesList,
+CommunitiesList > * {
     background-color: #2e3649;
     border: none;
 }
@@ -61,27 +75,17 @@ RoomList > * {
     background-color: #2e3649;
 }
 
-CommunitiesList,
-CommunitiesList > * {
-    background-color: #2e3649;
-}
-
 FlatButton {
     qproperty-foregroundColor: #495057;
 }
 
+AudioItem,
 FileItem {
     qproperty-textColor: #333;
     qproperty-backgroundColor: #f2f2f2;
     qproperty-iconColor: white;
 }
 
-AudioItem {
-    qproperty-textColor: #333;
-    qproperty-backgroundColor: #f2f2f2;
-    qproperty-iconColor: white;
-}
-
 RaisedButton {
     qproperty-foregroundColor: white;
 }
@@ -89,7 +93,7 @@ RaisedButton {
 RoomInfoListItem {
     qproperty-mentionedColor: #a82353;
     qproperty-highlightedBackgroundColor: #38A3D8;
-    qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
+    qproperty-hoverBackgroundColor: rgba(200, 200, 200, 40);
     qproperty-hoverTitleColor: #f2f5f8;
     qproperty-hoverSubtitleColor: white;
     qproperty-backgroundColor: #f2f5f8;
@@ -107,16 +111,18 @@ RoomInfoListItem {
     qproperty-highlightedTimestampColor: #f4f4f5;
     qproperty-hoverTimestampColor: white;
 
-    qproperty-avatarBgColor: #eee;
-    qproperty-avatarFgColor: black;
-
     qproperty-bubbleFgColor: white;
     qproperty-bubbleBgColor: #38A3D8;
 }
 
+RoomInfoListItem > Avatar {
+    qproperty-backgroundColor: #eee;
+    qproperty-textColor: black;
+}
+
 CommunitiesListItem {
     qproperty-highlightedBackgroundColor: #38A3D8;
-    qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
+    qproperty-hoverBackgroundColor: rgba(200, 200, 200, 40);
     qproperty-backgroundColor: #f2f5f8;
 
     qproperty-avatarBgColor: #eee;
@@ -141,14 +147,6 @@ UserInfoWidget {
     border-bottom: 2px solid #ccc;
 }
 
-UserSettingsPage {
-    background-color: white;
-}
-
-#UserSettingScrollWidget {
-    background-color: white;
-}
-
 Avatar {
     qproperty-textColor: black;
     qproperty-backgroundColor: #eee;
@@ -196,12 +194,22 @@ emoji--Panel > * {
     color: #333;
 }
 
+emoji--Panel QWidget { border: none; }
+emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
+emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
+
+emoji--Category {
+    qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
+}
+
 emoji--Category,
 emoji--Category > * {
     background-color: white;
     color: #ccc;
 }
 
+emoji--Category QLabel { margin: 20px 0 20px 8px; }
+
 FloatingButton {
     qproperty-backgroundColor: #efefef;
     qproperty-foregroundColor: black;
@@ -244,3 +252,5 @@ SnackBar {
     qproperty-textColor: white;
     qproperty-bgColor: #495057;
 }
+
+QSplitter::handle { image: none; }
diff --git a/resources/styles/system.qss b/resources/styles/system.qss
index c1e8898a..3ae3147a 100644
--- a/resources/styles/system.qss
+++ b/resources/styles/system.qss
@@ -1,3 +1,16 @@
+#chatPage,
+#chatPage > *,
+CommunitiesList,
+CommunitiesList > *,
+RoomList,
+RoomList > *,
+TimelineView,
+TimelineView > *,
+UserMentionsWidget,
+UserMentionsWidget > * {
+    border: none;
+}
+
 TypingDisplay {
     qproperty-textColor: palette(text);
     qproperty-backgroundColor: palette(window);
@@ -7,21 +20,18 @@ TimelineItem {
     qproperty-backgroundColor: palette(window);
 }
 
-TimelineView,
-TimelineView > * {
-    border: none;
+UserMentionsWidget > TimelineItem {
+    qproperty-backgroundColor: palette(window);
+    qproperty-hoverColor: palette(base);
 }
 
+SideBarActions,
 TextInputWidget {
     border: none;
     border-top: 1px solid palette(mid);
 }
 
-SideBarActions {
-    border: none;
-    border-top: 1px solid palette(mid);
-}
-
+UserInfoWidget,
 TopRoomBar {
     border: none;
     border-bottom: 1px solid palette(mid);
@@ -33,11 +43,6 @@ RoomList > * {
     border: none;
 }
 
-UserInfoWidget {
-    border: none;
-    border-bottom: 1px solid palette(mid);
-}
-
 #sideBar {
     border: none;
     border-right: 1px solid palette(mid);
@@ -57,18 +62,13 @@ FlatButton {
     qproperty-foregroundColor: palette(text);
 }
 
+AudioItem,
 FileItem {
     qproperty-textColor: palette(text);
     qproperty-backgroundColor: palette(base);
     qproperty-iconColor: palette(window);
 }
 
-AudioItem {
-    qproperty-textColor: palette(text);
-    qproperty-backgroundColor: palette(base);
-    qproperty-iconColor: palette(window);
-}
-
 RaisedButton {
     qproperty-foregroundColor: palette(buttonText);
 }
@@ -85,10 +85,11 @@ QListWidget {
     background-color: palette(window);
 }
 
-RoomInfoListItem {
+RoomInfoListItem,
+UserMentionsWidget {
     qproperty-mentionedColor: palette(alternate-base);
     qproperty-highlightedBackgroundColor: palette(highlight);
-    qproperty-hoverBackgroundColor: palette(base);
+    qproperty-hoverBackgroundColor: palette(light);
     qproperty-backgroundColor: palette(window);
 
     qproperty-titleColor: palette(text);
@@ -107,16 +108,19 @@ RoomInfoListItem {
     qproperty-highlightedTimestampColor: palette(highlightedtext);
     qproperty-hoverTimestampColor: palette(highlightedtext);
 
-    qproperty-avatarBgColor: palette(base);
-    qproperty-avatarFgColor: palette(text);
-
     qproperty-bubbleBgColor: palette(base);
     qproperty-bubbleFgColor: palette(text);
 }
 
+RoomInfoListItem > Avatar {
+    qproperty-backgroundColor: palette(base);
+    qproperty-textColor: palette(text);
+}
+
+
 CommunitiesListItem {
     qproperty-highlightedBackgroundColor: palette(highlight);
-    qproperty-hoverBackgroundColor: palette(base);
+    qproperty-hoverBackgroundColor: palette(light);
     qproperty-backgroundColor: palette(window);
 
     qproperty-avatarBgColor: palette(base);
@@ -131,6 +135,30 @@ LoadingIndicator {
     qproperty-color: palette(light);
 }
 
+emoji--Panel,
+emoji--Panel > * {
+    background-color: palette(base);
+    color: palette(text);
+}
+
+emoji--Panel QWidget { border: none; }
+emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
+emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
+
+emoji--Category {
+    qproperty-hoverBackgroundColor: palette(highlight);
+}
+
+emoji--Category,
+emoji--Category > * {
+    background-color: palette(window);
+    color: palette(text);
+}
+
+emoji--Category QLabel {
+    margin: 20px 0 20px 8px;
+}
+
 FloatingButton {
     qproperty-backgroundColor: palette(base);
     qproperty-foregroundColor: palette(text);
@@ -141,13 +169,11 @@ SnackBar {
     qproperty-bgColor: palette(base);
 }
 
-MemberItem {
-    background-color: palette(window);
-}
-
 Toggle {
     qproperty-activeColor: palette(highlight);
     qproperty-disabledColor: palette(dark);
     qproperty-inactiveColor: palette(mid);
     qproperty-trackColor: palette(base);
 }
+
+QSplitter::handle { image: none; }
diff --git a/scripts/emoji_codegen.py b/scripts/emoji_codegen.py
index cfa72425..634887b2 100755
--- a/scripts/emoji_codegen.py
+++ b/scripts/emoji_codegen.py
@@ -1,31 +1,19 @@
 #!/usr/bin/env python3
 
 import sys
-import json
+import re
 
 from jinja2 import Template
 
 
 class Emoji(object):
-    def __init__(self, code, shortname, category, order):
-        self.code = ''.join(list(map(code_to_bytes, code.split('-'))))
+    def __init__(self, code, shortname):
+        self.code = repr(code.encode('utf-8'))[1:].strip("'")
         self.shortname = shortname
-        self.category = category
-        self.order = int(order)
-
-
-def code_to_bytes(codepoint):
-    '''
-    Convert hex unicode codepoint to hex byte array.
-    '''
-    bytes = chr(int(codepoint, 16)).encode('utf-8')
-
-    return str(bytes)[1:].strip("'")
-
 
 def generate_code(emojis, category):
     tmpl = Template('''
-const QList EmojiProvider::{{ category }} = {
+const std::vector emoji::Provider::{{ category }} = {
     {%- for e in emoji %}
         Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}"},
     {%- endfor %}
@@ -38,44 +26,56 @@ const QList EmojiProvider::{{ category }} = {
 
 if __name__ == '__main__':
     if len(sys.argv) < 2:
-        print('usage: emoji_codegen.py /path/to/emoji.json')
+        print('usage: emoji_codegen.py /path/to/emoji-test.txt')
         sys.exit(1)
 
     filename = sys.argv[1]
-    data = {}
 
-    with open(filename, 'r') as filename:
-        data = json.loads(filename.read())
+    people = []
+    nature = []
+    food = []
+    activity = []
+    travel = []
+    objects = []
+    symbols = []
+    flags = []
 
-    emojis = []
+    categories = {
+        'Smileys & Emotion': people,
+        'People & Body': people,
+        'Animals & Nature': nature,
+        'Food & Drink': food,
+        'Travel & Places': travel,
+        'Activities': activity,
+        'Objects': objects,
+        'Symbols': symbols,
+        'Flags': flags
+    }
 
-    for emoji_name in data:
-        tmp = data[emoji_name]
+    current_category = ''
+    for line in open(filename, 'r'):
+        if line.startswith('# group:'):
+            current_category = line.split(':', 1)[1].strip()
 
-        l = len(tmp['unicode'].split('-'))
-
-        if l > 1 and tmp['category'] == 'people':
+        if not line or line.startswith('#'):
             continue
 
-        emojis.append(
-            Emoji(
-                tmp['unicode'],
-                tmp['shortname'],
-                tmp['category'],
-                tmp['emoji_order']
-            )
-        )
+        segments = re.split(r'\s+[#;] ', line.strip())
+        if len(segments) != 3:
+            continue
 
-    emojis.sort(key=lambda x: x.order)
+        code, qualification, charAndName = segments
 
-    people = list(filter(lambda x: x.category == "people", emojis))
-    nature = list(filter(lambda x: x.category == "nature", emojis))
-    food = list(filter(lambda x: x.category == "food", emojis))
-    activity = list(filter(lambda x: x.category == "activity", emojis))
-    travel = list(filter(lambda x: x.category == "travel", emojis))
-    objects = list(filter(lambda x: x.category == "objects", emojis))
-    symbols = list(filter(lambda x: x.category == "symbols", emojis))
-    flags = list(filter(lambda x: x.category == "flags", emojis))
+        # skip fully qualified versions of same unicode
+        if code.endswith('FE0F'):
+            continue
+
+        if qualification == 'component':
+            continue
+
+        char, name = re.match(r'^(\S+) E\d+\.\d+ (.*)$', charAndName).groups()
+
+        categories[current_category].append(Emoji(char, name))
 
     # Use xclip to pipe the output to clipboard.
     # e.g ./codegen.py emoji.json | xclip -sel clip
diff --git a/scripts/update_emoji.md b/scripts/update_emoji.md
new file mode 100644
index 00000000..00fe8c4e
--- /dev/null
+++ b/scripts/update_emoji.md
@@ -0,0 +1,7 @@
+# Updating emoji
+
+1. Get the latest emoji-test.txt from here: https://unicode.org/Public/emoji/
+2. Overwrite the existing resources/emoji-test.txt with the new one
+3. Run `./scripts/emoji_codegen.py resources/emoji-test.txt` and replace the current tail of src/emoji/Provider.cpp with the new output
+4. `make lint`
+5. Compile and test
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp
index 57b61c75..d0556f85 100644
--- a/src/AvatarProvider.cpp
+++ b/src/AvatarProvider.cpp
@@ -16,30 +16,37 @@
  */
 
 #include 
+#include 
 #include 
+#include 
 
 #include "AvatarProvider.h"
 #include "Cache.h"
 #include "Logging.h"
 #include "MatrixClient.h"
 
+static QPixmapCache avatar_cache;
+
 namespace AvatarProvider {
-
 void
-resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback callback)
+resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback)
 {
-        const auto key       = QString("%1 %2").arg(room_id).arg(user_id);
-        const auto avatarUrl = Cache::avatarUrl(room_id, user_id);
-
-        if (!Cache::AvatarUrls.contains(key) || !cache::client())
-                return;
+        const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
 
         if (avatarUrl.isEmpty())
                 return;
 
-        auto data = cache::client()->image(avatarUrl);
+        QPixmap pixmap;
+        if (avatar_cache.find(cacheKey, &pixmap)) {
+                callback(pixmap);
+                return;
+        }
+
+        auto data = cache::image(cacheKey);
         if (!data.isNull()) {
-                callback(QImage::fromData(data));
+                pixmap.loadFromData(data);
+                avatar_cache.insert(cacheKey, pixmap);
+                callback(pixmap);
                 return;
         }
 
@@ -47,16 +54,22 @@ resolve(const QString &room_id, const QString &user_id, QObject *receiver, Avata
         QObject::connect(proxy.get(),
                          &AvatarProxy::avatarDownloaded,
                          receiver,
-                         [callback](const QByteArray &data) { callback(QImage::fromData(data)); });
+                         [callback, cacheKey](const QByteArray &data) {
+                                 QPixmap pm;
+                                 pm.loadFromData(data);
+                                 avatar_cache.insert(cacheKey, pm);
+                                 callback(pm);
+                         });
 
         mtx::http::ThumbOpts opts;
-        opts.width   = 256;
-        opts.height  = 256;
+        opts.width   = size;
+        opts.height  = size;
         opts.mxc_url = avatarUrl.toStdString();
 
         http::client()->get_thumbnail(
           opts,
-          [opts, proxy = std::move(proxy)](const std::string &res, mtx::http::RequestErr err) {
+          [opts, cacheKey, proxy = std::move(proxy)](const std::string &res,
+                                                     mtx::http::RequestErr err) {
                   if (err) {
                           nhlog::net()->warn("failed to download avatar: {} - ({} {})",
                                              opts.mxc_url,
@@ -65,10 +78,21 @@ resolve(const QString &room_id, const QString &user_id, QObject *receiver, Avata
                           return;
                   }
 
-                  cache::client()->saveImage(opts.mxc_url, res);
+                  cache::saveImage(cacheKey.toStdString(), res);
 
-                  auto data = QByteArray(res.data(), res.size());
-                  emit proxy->avatarDownloaded(data);
+                  emit proxy->avatarDownloaded(QByteArray(res.data(), res.size()));
           });
 }
+
+void
+resolve(const QString &room_id,
+        const QString &user_id,
+        int size,
+        QObject *receiver,
+        AvatarCallback callback)
+{
+        const auto avatarUrl = cache::avatarUrl(room_id, user_id);
+
+        resolve(avatarUrl, size, receiver, callback);
+}
 }
diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h
index 4b4e15e9..47ed028e 100644
--- a/src/AvatarProvider.h
+++ b/src/AvatarProvider.h
@@ -17,7 +17,7 @@
 
 #pragma once
 
-#include 
+#include 
 #include 
 
 class AvatarProxy : public QObject
@@ -28,9 +28,15 @@ signals:
         void avatarDownloaded(const QByteArray &data);
 };
 
-using AvatarCallback = std::function;
+using AvatarCallback = std::function;
 
 namespace AvatarProvider {
 void
-resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback cb);
+resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback cb);
+void
+resolve(const QString &room_id,
+        const QString &user_id,
+        int size,
+        QObject *receiver,
+        AvatarCallback cb);
 }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 81054ddc..0f33a276 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -17,17 +17,21 @@
 
 #include 
 #include 
+#include 
 
 #include 
+#include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
-#include 
 #include 
 
 #include "Cache.h"
+#include "Cache_p.h"
+#include "Logging.h"
 #include "Utils.h"
 
 //! Should be changed when a breaking change occurs in the cache format.
@@ -35,13 +39,13 @@
 static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.09.21");
 static const std::string SECRET("secret");
 
-static const lmdb::val NEXT_BATCH_KEY("next_batch");
-static const lmdb::val OLM_ACCOUNT_KEY("olm_account");
-static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version");
+static lmdb::val NEXT_BATCH_KEY("next_batch");
+static lmdb::val OLM_ACCOUNT_KEY("olm_account");
+static lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version");
 
-constexpr size_t MAX_RESTORED_MESSAGES = 30;
+constexpr size_t MAX_RESTORED_MESSAGES = 30'000;
 
-constexpr auto DB_SIZE = 512UL * 1024UL * 1024UL; // 512 MB
+constexpr auto DB_SIZE = 32ULL * 1024ULL * 1024ULL * 1024ULL; // 32 GB
 constexpr auto MAX_DBS = 8092UL;
 
 //! Cache databases and their format.
@@ -76,32 +80,30 @@ constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
 using CachedReceipts = std::multimap>;
 using Receipts       = std::map>;
 
+Q_DECLARE_METATYPE(SearchResult)
+Q_DECLARE_METATYPE(std::vector)
+Q_DECLARE_METATYPE(RoomMember)
+Q_DECLARE_METATYPE(mtx::responses::Timeline)
+Q_DECLARE_METATYPE(RoomSearchResult)
+Q_DECLARE_METATYPE(RoomInfo)
+
 namespace {
 std::unique_ptr instance_ = nullptr;
 }
 
-namespace cache {
-void
-init(const QString &user_id)
+int
+numeric_key_comparison(const MDB_val *a, const MDB_val *b)
 {
-        qRegisterMetaType();
-        qRegisterMetaType>();
-        qRegisterMetaType();
-        qRegisterMetaType();
-        qRegisterMetaType();
-        qRegisterMetaType>();
-        qRegisterMetaType>();
-        qRegisterMetaType>();
+        auto lhs = std::stoull(std::string((char *)a->mv_data, a->mv_size));
+        auto rhs = std::stoull(std::string((char *)b->mv_data, b->mv_size));
 
-        instance_ = std::make_unique(user_id);
-}
+        if (lhs < rhs)
+                return 1;
+        else if (lhs == rhs)
+                return 0;
 
-Cache *
-client()
-{
-        return instance_.get();
+        return -1;
 }
-} // namespace cache
 
 Cache::Cache(const QString &userId, QObject *parent)
   : QObject{parent}
@@ -392,7 +394,7 @@ Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr
         txn.commit();
 }
 
-boost::optional
+std::optional
 Cache::getOlmSession(const std::string &curve25519, const std::string &session_id)
 {
         using namespace mtx::crypto;
@@ -410,7 +412,7 @@ Cache::getOlmSession(const std::string &curve25519, const std::string &session_i
                 return unpickle(data, SECRET);
         }
 
-        return boost::none;
+        return std::nullopt;
 }
 
 std::vector
@@ -913,13 +915,17 @@ Cache::calculateRoomReadStatus(const std::string &room_id)
         auto txn = lmdb::txn::begin(env_);
 
         // Get last event id on the room.
-        const auto last_event_id = getLastMessageInfo(txn, room_id).event_id;
+        const auto last_event_id = getLastEventId(txn, room_id);
         const auto localUser     = utils::localUser().toStdString();
 
         txn.commit();
 
+        if (last_event_id.empty())
+                return false;
+
         // Retrieve all read receipts for that event.
-        const auto receipts = readReceipts(last_event_id, QString::fromStdString(room_id));
+        const auto receipts =
+          readReceipts(QString::fromStdString(last_event_id), QString::fromStdString(room_id));
 
         if (receipts.size() == 0)
                 return true;
@@ -958,13 +964,14 @@ Cache::saveState(const mtx::responses::Sync &res)
                 updatedInfo.avatar_url =
                   getRoomAvatarUrl(txn, statesdb, membersdb, QString::fromStdString(room.first))
                     .toStdString();
+                updatedInfo.version = getRoomVersion(txn, statesdb).toStdString();
 
                 // Process the account_data associated with this room
                 bool has_new_tags = false;
                 for (const auto &evt : room.second.account_data.events) {
                         // for now only fetch tag events
-                        if (evt.type() == typeid(Event)) {
-                                auto tags_evt = boost::get>(evt);
+                        if (std::holds_alternative>(evt)) {
+                                auto tags_evt = std::get>(evt);
                                 has_new_tags  = true;
                                 for (const auto &tag : tags_evt.content.tags) {
                                         updatedInfo.tags.push_back(tag.first);
@@ -1045,19 +1052,17 @@ Cache::saveInvite(lmdb::txn &txn,
         using namespace mtx::events::state;
 
         for (const auto &e : room.invite_state) {
-                if (boost::get>(&e) != nullptr) {
-                        auto msg = boost::get>(e);
+                if (auto msg = std::get_if>(&e)) {
+                        auto display_name = msg->content.display_name.empty()
+                                              ? msg->state_key
+                                              : msg->content.display_name;
 
-                        auto display_name = msg.content.display_name.empty()
-                                              ? msg.state_key
-                                              : msg.content.display_name;
-
-                        MemberInfo tmp{display_name, msg.content.avatar_url};
+                        MemberInfo tmp{display_name, msg->content.avatar_url};
 
                         lmdb::dbi_put(
-                          txn, membersdb, lmdb::val(msg.state_key), lmdb::val(json(tmp).dump()));
+                          txn, membersdb, lmdb::val(msg->state_key), lmdb::val(json(tmp).dump()));
                 } else {
-                        boost::apply_visitor(
+                        std::visit(
                           [&txn, &statesdb](auto msg) {
                                   bool res = lmdb::dbi_put(txn,
                                                            statesdb,
@@ -1065,8 +1070,8 @@ Cache::saveInvite(lmdb::txn &txn,
                                                            lmdb::val(json(msg).dump()));
 
                                   if (!res)
-                                          std::cout << "couldn't save data" << json(msg).dump()
-                                                    << '\n';
+                                          nhlog::db()->warn("couldn't save data: {}",
+                                                            json(msg).dump());
                           },
                           e);
                 }
@@ -1118,7 +1123,7 @@ Cache::roomsWithTagUpdates(const mtx::responses::Sync &res)
         for (const auto &room : res.rooms.join) {
                 bool hasUpdates = false;
                 for (const auto &evt : room.second.account_data.events) {
-                        if (evt.type() == typeid(Event)) {
+                        if (std::holds_alternative>(evt)) {
                                 hasUpdates = true;
                         }
                 }
@@ -1230,9 +1235,31 @@ Cache::roomMessages()
         return msgs;
 }
 
+QMap
+Cache::getTimelineMentions()
+{
+        // TODO: Should be read-only, but getMentionsDb will attempt to create a DB
+        // if it doesn't exist, throwing an error.
+        auto txn = lmdb::txn::begin(env_, nullptr);
+
+        QMap notifs;
+
+        auto room_ids = getRoomIds(txn);
+
+        for (const auto &room_id : room_ids) {
+                auto roomNotifs                         = getTimelineMentionsForRoom(txn, room_id);
+                notifs[QString::fromStdString(room_id)] = roomNotifs;
+        }
+
+        txn.commit();
+
+        return notifs;
+}
+
 mtx::responses::Timeline
 Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id)
 {
+        // TODO(nico): Limit the messages returned by this maybe?
         auto db = getMessagesDb(txn, room_id);
 
         mtx::responses::Timeline timeline;
@@ -1300,6 +1327,31 @@ Cache::roomInfo(bool withInvites)
         return result;
 }
 
+std::string
+Cache::getLastEventId(lmdb::txn &txn, const std::string &room_id)
+{
+        auto db = getMessagesDb(txn, room_id);
+
+        if (db.size(txn) == 0)
+                return {};
+
+        std::string timestamp, msg;
+
+        auto cursor = lmdb::cursor::open(txn, db);
+        while (cursor.get(timestamp, msg, MDB_NEXT)) {
+                auto obj = json::parse(msg);
+
+                if (obj.count("event") == 0)
+                        continue;
+
+                cursor.close();
+                return obj["event"]["event_id"];
+        }
+        cursor.close();
+
+        return {};
+}
+
 DescInfo
 Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
 {
@@ -1311,13 +1363,15 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
         std::string timestamp, msg;
 
         QSettings settings;
-        auto local_user = settings.value("auth/user_id").toString();
+        const auto local_user = utils::localUser();
 
         auto cursor = lmdb::cursor::open(txn, db);
         while (cursor.get(timestamp, msg, MDB_NEXT)) {
                 auto obj = json::parse(msg);
 
-                if (obj.count("event") == 0)
+                if (obj.count("event") == 0 || !(obj["event"]["type"] == "m.room.message" ||
+                                                 obj["event"]["type"] == "m.sticker" ||
+                                                 obj["event"]["type"] == "m.room.encrypted"))
                         continue;
 
                 mtx::events::collections::TimelineEvent event;
@@ -1481,7 +1535,7 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
         return "Empty Room";
 }
 
-JoinRule
+mtx::events::state::JoinRule
 Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
         using namespace mtx::events;
@@ -1493,14 +1547,14 @@ Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
 
         if (res) {
                 try {
-                        StateEvent msg =
+                        StateEvent msg =
                           json::parse(std::string(event.data(), event.size()));
                         return msg.content.join_rule;
                 } catch (const json::exception &e) {
                         nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what());
                 }
         }
-        return JoinRule::Knock;
+        return state::JoinRule::Knock;
 }
 
 bool
@@ -1551,6 +1605,32 @@ Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
         return QString();
 }
 
+QString
+Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        using namespace mtx::events;
+        using namespace mtx::events::state;
+
+        lmdb::val event;
+        bool res = lmdb::dbi_get(
+          txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCreate)), event);
+
+        if (res) {
+                try {
+                        StateEvent msg =
+                          json::parse(std::string(event.data(), event.size()));
+
+                        if (!msg.content.room_version.empty())
+                                return QString::fromStdString(msg.content.room_version);
+                } catch (const json::exception &e) {
+                        nhlog::db()->warn("failed to parse m.room.create event: {}", e.what());
+                }
+        }
+
+        nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\"");
+        return QString("1");
+}
+
 QString
 Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
@@ -1779,10 +1859,7 @@ Cache::searchRooms(const std::string &query, std::uint8_t max_items)
 
         std::vector results;
         for (auto it = items.begin(); it != end; it++) {
-                results.push_back(
-                  RoomSearchResult{it->second.first,
-                                   it->second.second,
-                                   QImage::fromData(image(txn, it->second.second.avatar_url))});
+                results.push_back(RoomSearchResult{it->second.first, it->second.second});
         }
 
         txn.commit();
@@ -1790,7 +1867,7 @@ Cache::searchRooms(const std::string &query, std::uint8_t max_items)
         return results;
 }
 
-QVector
+std::vector
 Cache::searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items)
 {
         std::multimap> items;
@@ -1813,7 +1890,7 @@ Cache::searchUsers(const std::string &room_id, const std::string &query, std::ui
         else if (items.size() > 0)
                 std::advance(end, items.size());
 
-        QVector results;
+        std::vector results;
         for (auto it = items.begin(); it != end; it++) {
                 const auto user = it->second;
                 results.push_back(SearchResult{QString::fromStdString(user.first),
@@ -1889,10 +1966,7 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
         using namespace mtx::events::state;
 
         for (const auto &e : res.events) {
-                if (isStateEvent(e))
-                        continue;
-
-                if (boost::get>(&e) != nullptr)
+                if (std::holds_alternative>(e))
                         continue;
 
                 json obj = json::object();
@@ -1907,6 +1981,88 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
         }
 }
 
+mtx::responses::Notifications
+Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id)
+{
+        auto db = getMentionsDb(txn, room_id);
+
+        if (db.size(txn) == 0) {
+                return mtx::responses::Notifications{};
+        }
+
+        mtx::responses::Notifications notif;
+        std::string event_id, msg;
+
+        auto cursor = lmdb::cursor::open(txn, db);
+
+        while (cursor.get(event_id, msg, MDB_NEXT)) {
+                auto obj = json::parse(msg);
+
+                if (obj.count("event") == 0)
+                        continue;
+
+                mtx::responses::Notification notification;
+                mtx::responses::from_json(obj, notification);
+
+                notif.notifications.push_back(notification);
+        }
+        cursor.close();
+
+        std::reverse(notif.notifications.begin(), notif.notifications.end());
+
+        return notif;
+}
+
+//! Add all notifications containing a user mention to the db.
+void
+Cache::saveTimelineMentions(const mtx::responses::Notifications &res)
+{
+        QMap> notifsByRoom;
+
+        // Sort into room-specific 'buckets'
+        for (const auto ¬if : res.notifications) {
+                json val = notif;
+                notifsByRoom[notif.room_id].push_back(notif);
+        }
+
+        auto txn = lmdb::txn::begin(env_);
+        // Insert the entire set of mentions for each room at a time.
+        QMap>::const_iterator it =
+          notifsByRoom.constBegin();
+        auto end = notifsByRoom.constEnd();
+        while (it != end) {
+                nhlog::db()->debug("Storing notifications for " + it.key());
+                saveTimelineMentions(txn, it.key(), std::move(it.value()));
+                ++it;
+        }
+
+        txn.commit();
+}
+
+void
+Cache::saveTimelineMentions(lmdb::txn &txn,
+                            const std::string &room_id,
+                            const QList &res)
+{
+        auto db = getMentionsDb(txn, room_id);
+
+        using namespace mtx::events;
+        using namespace mtx::events::state;
+
+        for (const auto ¬if : res) {
+                const auto event_id = utils::event_id(notif.event);
+
+                // double check that we have the correct room_id...
+                if (room_id.compare(notif.room_id) != 0) {
+                        return;
+                }
+
+                json obj = notif;
+
+                lmdb::dbi_put(txn, db, lmdb::val(event_id), lmdb::val(obj.dump()));
+        }
+}
+
 void
 Cache::markSentNotification(const std::string &event_id)
 {
@@ -2059,7 +2215,6 @@ Cache::roomMembers(const std::string &room_id)
 
 QHash Cache::DisplayNames;
 QHash Cache::AvatarUrls;
-QHash Cache::UserColors;
 
 QString
 Cache::displayName(const QString &room_id, const QString &user_id)
@@ -2091,16 +2246,6 @@ Cache::avatarUrl(const QString &room_id, const QString &user_id)
         return QString();
 }
 
-QString
-Cache::userColor(const QString &user_id)
-{
-        if (UserColors.contains(user_id)) {
-                return UserColors[user_id];
-        }
-
-        return QString();
-}
-
 void
 Cache::insertDisplayName(const QString &room_id,
                          const QString &user_id,
@@ -2132,19 +2277,604 @@ Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
 }
 
 void
-Cache::insertUserColor(const QString &user_id, const QString &color_name)
+to_json(json &j, const RoomInfo &info)
 {
-        UserColors.insert(user_id, color_name);
+        j["name"]         = info.name;
+        j["topic"]        = info.topic;
+        j["avatar_url"]   = info.avatar_url;
+        j["version"]      = info.version;
+        j["is_invite"]    = info.is_invite;
+        j["join_rule"]    = info.join_rule;
+        j["guest_access"] = info.guest_access;
+
+        if (info.member_count != 0)
+                j["member_count"] = info.member_count;
+
+        if (info.tags.size() != 0)
+                j["tags"] = info.tags;
 }
 
 void
-Cache::removeUserColor(const QString &user_id)
+from_json(const json &j, RoomInfo &info)
 {
-        UserColors.remove(user_id);
+        info.name       = j.at("name");
+        info.topic      = j.at("topic");
+        info.avatar_url = j.at("avatar_url");
+        info.version    = j.value(
+          "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString());
+        info.is_invite    = j.at("is_invite");
+        info.join_rule    = j.at("join_rule");
+        info.guest_access = j.at("guest_access");
+
+        if (j.count("member_count"))
+                info.member_count = j.at("member_count");
+
+        if (j.count("tags"))
+                info.tags = j.at("tags").get>();
 }
 
 void
-Cache::clearUserColors()
+to_json(json &j, const ReadReceiptKey &key)
 {
-        UserColors.clear();
+        j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
 }
+
+void
+from_json(const json &j, ReadReceiptKey &key)
+{
+        key.event_id = j.at("event_id").get();
+        key.room_id  = j.at("room_id").get();
+}
+
+void
+to_json(json &j, const MemberInfo &info)
+{
+        j["name"]       = info.name;
+        j["avatar_url"] = info.avatar_url;
+}
+
+void
+from_json(const json &j, MemberInfo &info)
+{
+        info.name       = j.at("name");
+        info.avatar_url = j.at("avatar_url");
+}
+
+void
+to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
+{
+        obj["session_id"]    = msg.session_id;
+        obj["session_key"]   = msg.session_key;
+        obj["message_index"] = msg.message_index;
+}
+
+void
+from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
+{
+        msg.session_id    = obj.at("session_id");
+        msg.session_key   = obj.at("session_key");
+        msg.message_index = obj.at("message_index");
+}
+
+void
+to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
+{
+        obj["ed25519"]    = msg.ed25519;
+        obj["curve25519"] = msg.curve25519;
+}
+
+void
+from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
+{
+        msg.ed25519    = obj.at("ed25519");
+        msg.curve25519 = obj.at("curve25519");
+}
+
+void
+to_json(nlohmann::json &obj, const MegolmSessionIndex &msg)
+{
+        obj["room_id"]    = msg.room_id;
+        obj["session_id"] = msg.session_id;
+        obj["sender_key"] = msg.sender_key;
+}
+
+void
+from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
+{
+        msg.room_id    = obj.at("room_id");
+        msg.session_id = obj.at("session_id");
+        msg.sender_key = obj.at("sender_key");
+}
+
+namespace cache {
+void
+init(const QString &user_id)
+{
+        qRegisterMetaType();
+        qRegisterMetaType>();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType();
+        qRegisterMetaType>();
+        qRegisterMetaType>();
+        qRegisterMetaType>();
+
+        instance_ = std::make_unique(user_id);
+}
+
+Cache *
+client()
+{
+        return instance_.get();
+}
+
+std::string
+displayName(const std::string &room_id, const std::string &user_id)
+{
+        return instance_->displayName(room_id, user_id);
+}
+
+QString
+displayName(const QString &room_id, const QString &user_id)
+{
+        return instance_->displayName(room_id, user_id);
+}
+QString
+avatarUrl(const QString &room_id, const QString &user_id)
+{
+        return instance_->avatarUrl(room_id, user_id);
+}
+
+void
+removeDisplayName(const QString &room_id, const QString &user_id)
+{
+        instance_->removeDisplayName(room_id, user_id);
+}
+void
+removeAvatarUrl(const QString &room_id, const QString &user_id)
+{
+        instance_->removeAvatarUrl(room_id, user_id);
+}
+
+void
+insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name)
+{
+        instance_->insertDisplayName(room_id, user_id, display_name);
+}
+void
+insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url)
+{
+        instance_->insertAvatarUrl(room_id, user_id, avatar_url);
+}
+
+//! Load saved data for the display names & avatars.
+void
+populateMembers()
+{
+        instance_->populateMembers();
+}
+
+std::vector
+joinedRooms()
+{
+        return instance_->joinedRooms();
+}
+
+QMap
+roomInfo(bool withInvites)
+{
+        return instance_->roomInfo(withInvites);
+}
+std::map
+invites()
+{
+        return instance_->invites();
+}
+
+QString
+getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
+{
+        return instance_->getRoomName(txn, statesdb, membersdb);
+}
+mtx::events::state::JoinRule
+getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomJoinRule(txn, statesdb);
+}
+bool
+getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomGuestAccess(txn, statesdb);
+}
+QString
+getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomTopic(txn, statesdb);
+}
+QString
+getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb, const QString &room_id)
+{
+        return instance_->getRoomAvatarUrl(txn, statesdb, membersdb, room_id);
+}
+
+QString
+getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomVersion(txn, statesdb);
+}
+
+std::vector
+getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
+{
+        return instance_->getMembers(room_id, startIndex, len);
+}
+
+void
+saveState(const mtx::responses::Sync &res)
+{
+        instance_->saveState(res);
+}
+bool
+isInitialized()
+{
+        return instance_->isInitialized();
+}
+
+std::string
+nextBatchToken()
+{
+        return instance_->nextBatchToken();
+}
+
+void
+deleteData()
+{
+        instance_->deleteData();
+}
+
+void
+removeInvite(lmdb::txn &txn, const std::string &room_id)
+{
+        instance_->removeInvite(txn, room_id);
+}
+void
+removeInvite(const std::string &room_id)
+{
+        instance_->removeInvite(room_id);
+}
+void
+removeRoom(lmdb::txn &txn, const std::string &roomid)
+{
+        instance_->removeRoom(txn, roomid);
+}
+void
+removeRoom(const std::string &roomid)
+{
+        instance_->removeRoom(roomid);
+}
+void
+removeRoom(const QString &roomid)
+{
+        instance_->removeRoom(roomid.toStdString());
+}
+void
+setup()
+{
+        instance_->setup();
+}
+
+bool
+isFormatValid()
+{
+        return instance_->isFormatValid();
+}
+void
+setCurrentFormat()
+{
+        instance_->setCurrentFormat();
+}
+
+std::map
+roomMessages()
+{
+        return instance_->roomMessages();
+}
+
+QMap
+getTimelineMentions()
+{
+        return instance_->getTimelineMentions();
+}
+
+//! Retrieve all the user ids from a room.
+std::vector
+roomMembers(const std::string &room_id)
+{
+        return instance_->roomMembers(room_id);
+}
+
+//! Check if the given user has power leve greater than than
+//! lowest power level of the given events.
+bool
+hasEnoughPowerLevel(const std::vector &eventTypes,
+                    const std::string &room_id,
+                    const std::string &user_id)
+{
+        return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id);
+}
+
+//! Retrieves the saved room avatar.
+QImage
+getRoomAvatar(const QString &id)
+{
+        return instance_->getRoomAvatar(id);
+}
+QImage
+getRoomAvatar(const std::string &id)
+{
+        return instance_->getRoomAvatar(id);
+}
+
+void
+updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
+{
+        instance_->updateReadReceipt(txn, room_id, receipts);
+}
+
+UserReceipts
+readReceipts(const QString &event_id, const QString &room_id)
+{
+        return instance_->readReceipts(event_id, room_id);
+}
+
+//! Filter the events that have at least one read receipt.
+std::vector
+filterReadEvents(const QString &room_id,
+                 const std::vector &event_ids,
+                 const std::string &excluded_user)
+{
+        return instance_->filterReadEvents(room_id, event_ids, excluded_user);
+}
+//! Add event for which we are expecting some read receipts.
+void
+addPendingReceipt(const QString &room_id, const QString &event_id)
+{
+        instance_->addPendingReceipt(room_id, event_id);
+}
+void
+removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id)
+{
+        instance_->removePendingReceipt(txn, room_id, event_id);
+}
+void
+notifyForReadReceipts(const std::string &room_id)
+{
+        instance_->notifyForReadReceipts(room_id);
+}
+std::vector
+pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id)
+{
+        return instance_->pendingReceiptsEvents(txn, room_id);
+}
+
+QByteArray
+image(const QString &url)
+{
+        return instance_->image(url);
+}
+QByteArray
+image(lmdb::txn &txn, const std::string &url)
+{
+        return instance_->image(txn, url);
+}
+void
+saveImage(const std::string &url, const std::string &data)
+{
+        instance_->saveImage(url, data);
+}
+void
+saveImage(const QString &url, const QByteArray &data)
+{
+        instance_->saveImage(url, data);
+}
+
+RoomInfo
+singleRoomInfo(const std::string &room_id)
+{
+        return instance_->singleRoomInfo(room_id);
+}
+std::vector
+roomsWithStateUpdates(const mtx::responses::Sync &res)
+{
+        return instance_->roomsWithStateUpdates(res);
+}
+std::vector
+roomsWithTagUpdates(const mtx::responses::Sync &res)
+{
+        return instance_->roomsWithTagUpdates(res);
+}
+std::map
+getRoomInfo(const std::vector &rooms)
+{
+        return instance_->getRoomInfo(rooms);
+}
+
+//! Calculates which the read status of a room.
+//! Whether all the events in the timeline have been read.
+bool
+calculateRoomReadStatus(const std::string &room_id)
+{
+        return instance_->calculateRoomReadStatus(room_id);
+}
+void
+calculateRoomReadStatus()
+{
+        instance_->calculateRoomReadStatus();
+}
+
+std::vector
+searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items)
+{
+        return instance_->searchUsers(room_id, query, max_items);
+}
+std::vector
+searchRooms(const std::string &query, std::uint8_t max_items)
+{
+        return instance_->searchRooms(query, max_items);
+}
+
+void
+markSentNotification(const std::string &event_id)
+{
+        instance_->markSentNotification(event_id);
+}
+//! Removes an event from the sent notifications.
+void
+removeReadNotification(const std::string &event_id)
+{
+        instance_->removeReadNotification(event_id);
+}
+//! Check if we have sent a desktop notification for the given event id.
+bool
+isNotificationSent(const std::string &event_id)
+{
+        return instance_->isNotificationSent(event_id);
+}
+
+//! Add all notifications containing a user mention to the db.
+void
+saveTimelineMentions(const mtx::responses::Notifications &res)
+{
+        instance_->saveTimelineMentions(res);
+}
+
+//! Remove old unused data.
+void
+deleteOldMessages()
+{
+        instance_->deleteOldMessages();
+}
+void
+deleteOldData() noexcept
+{
+        instance_->deleteOldData();
+}
+//! Retrieve all saved room ids.
+std::vector
+getRoomIds(lmdb::txn &txn)
+{
+        return instance_->getRoomIds(txn);
+}
+
+//! Mark a room that uses e2e encryption.
+void
+setEncryptedRoom(lmdb::txn &txn, const std::string &room_id)
+{
+        instance_->setEncryptedRoom(txn, room_id);
+}
+bool
+isRoomEncrypted(const std::string &room_id)
+{
+        return instance_->isRoomEncrypted(room_id);
+}
+
+//! Check if a user is a member of the room.
+bool
+isRoomMember(const std::string &user_id, const std::string &room_id)
+{
+        return instance_->isRoomMember(user_id, room_id);
+}
+
+//
+// Outbound Megolm Sessions
+//
+void
+saveOutboundMegolmSession(const std::string &room_id,
+                          const OutboundGroupSessionData &data,
+                          mtx::crypto::OutboundGroupSessionPtr session)
+{
+        instance_->saveOutboundMegolmSession(room_id, data, std::move(session));
+}
+OutboundGroupSessionDataRef
+getOutboundMegolmSession(const std::string &room_id)
+{
+        return instance_->getOutboundMegolmSession(room_id);
+}
+bool
+outboundMegolmSessionExists(const std::string &room_id) noexcept
+{
+        return instance_->outboundMegolmSessionExists(room_id);
+}
+void
+updateOutboundMegolmSession(const std::string &room_id, int message_index)
+{
+        instance_->updateOutboundMegolmSession(room_id, message_index);
+}
+
+void
+importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
+{
+        instance_->importSessionKeys(keys);
+}
+mtx::crypto::ExportedSessionKeys
+exportSessionKeys()
+{
+        return instance_->exportSessionKeys();
+}
+
+//
+// Inbound Megolm Sessions
+//
+void
+saveInboundMegolmSession(const MegolmSessionIndex &index,
+                         mtx::crypto::InboundGroupSessionPtr session)
+{
+        instance_->saveInboundMegolmSession(index, std::move(session));
+}
+OlmInboundGroupSession *
+getInboundMegolmSession(const MegolmSessionIndex &index)
+{
+        return instance_->getInboundMegolmSession(index);
+}
+bool
+inboundMegolmSessionExists(const MegolmSessionIndex &index)
+{
+        return instance_->inboundMegolmSessionExists(index);
+}
+
+//
+// Olm Sessions
+//
+void
+saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session)
+{
+        instance_->saveOlmSession(curve25519, std::move(session));
+}
+std::vector
+getOlmSessions(const std::string &curve25519)
+{
+        return instance_->getOlmSessions(curve25519);
+}
+std::optional
+getOlmSession(const std::string &curve25519, const std::string &session_id)
+{
+        return instance_->getOlmSession(curve25519, session_id);
+}
+
+void
+saveOlmAccount(const std::string &pickled)
+{
+        instance_->saveOlmAccount(pickled);
+}
+std::string
+restoreOlmAccount()
+{
+        return instance_->restoreOlmAccount();
+}
+
+void
+restoreSessions()
+{
+        return instance_->restoreSessions();
+}
+} // namespace cache
diff --git a/src/Cache.h b/src/Cache.h
index b9cf0aeb..bb042ea9 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -17,716 +17,280 @@
 
 #pragma once
 
-#include 
-
 #include 
 #include 
 #include 
 #include 
 
+#if __has_include()
+#include 
+#else
 #include 
-#include 
+#endif
+
 #include 
-#include 
-#include 
-#include 
 
-#include "Logging.h"
-
-using mtx::events::state::JoinRule;
-
-struct RoomMember
-{
-        QString user_id;
-        QString display_name;
-        QImage avatar;
-};
-
-struct SearchResult
-{
-        QString user_id;
-        QString display_name;
-};
-
-static int
-numeric_key_comparison(const MDB_val *a, const MDB_val *b)
-{
-        auto lhs = std::stoull(std::string((char *)a->mv_data, a->mv_size));
-        auto rhs = std::stoull(std::string((char *)b->mv_data, b->mv_size));
-
-        if (lhs < rhs)
-                return 1;
-        else if (lhs == rhs)
-                return 0;
-
-        return -1;
-}
-
-Q_DECLARE_METATYPE(SearchResult)
-Q_DECLARE_METATYPE(QVector)
-Q_DECLARE_METATYPE(RoomMember)
-Q_DECLARE_METATYPE(mtx::responses::Timeline)
-
-//! Used to uniquely identify a list of read receipts.
-struct ReadReceiptKey
-{
-        std::string event_id;
-        std::string room_id;
-};
-
-inline void
-to_json(json &j, const ReadReceiptKey &key)
-{
-        j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
-}
-
-inline void
-from_json(const json &j, ReadReceiptKey &key)
-{
-        key.event_id = j.at("event_id").get();
-        key.room_id  = j.at("room_id").get();
-}
-
-struct DescInfo
-{
-        QString event_id;
-        QString username;
-        QString userid;
-        QString body;
-        QString timestamp;
-        QDateTime datetime;
-};
-
-//! UI info associated with a room.
-struct RoomInfo
-{
-        //! The calculated name of the room.
-        std::string name;
-        //! The topic of the room.
-        std::string topic;
-        //! The calculated avatar url of the room.
-        std::string avatar_url;
-        //! Whether or not the room is an invite.
-        bool is_invite = false;
-        //! Total number of members in the room.
-        int16_t member_count = 0;
-        //! Who can access to the room.
-        JoinRule join_rule = JoinRule::Public;
-        bool guest_access  = false;
-        //! Metadata describing the last message in the timeline.
-        DescInfo msgInfo;
-        //! The list of tags associated with this room
-        std::vector tags;
-};
-
-inline void
-to_json(json &j, const RoomInfo &info)
-{
-        j["name"]         = info.name;
-        j["topic"]        = info.topic;
-        j["avatar_url"]   = info.avatar_url;
-        j["is_invite"]    = info.is_invite;
-        j["join_rule"]    = info.join_rule;
-        j["guest_access"] = info.guest_access;
-
-        if (info.member_count != 0)
-                j["member_count"] = info.member_count;
-
-        if (info.tags.size() != 0)
-                j["tags"] = info.tags;
-}
-
-inline void
-from_json(const json &j, RoomInfo &info)
-{
-        info.name         = j.at("name");
-        info.topic        = j.at("topic");
-        info.avatar_url   = j.at("avatar_url");
-        info.is_invite    = j.at("is_invite");
-        info.join_rule    = j.at("join_rule");
-        info.guest_access = j.at("guest_access");
-
-        if (j.count("member_count"))
-                info.member_count = j.at("member_count");
-
-        if (j.count("tags"))
-                info.tags = j.at("tags").get>();
-}
-
-//! Basic information per member;
-struct MemberInfo
-{
-        std::string name;
-        std::string avatar_url;
-};
-
-inline void
-to_json(json &j, const MemberInfo &info)
-{
-        j["name"]       = info.name;
-        j["avatar_url"] = info.avatar_url;
-}
-
-inline void
-from_json(const json &j, MemberInfo &info)
-{
-        info.name       = j.at("name");
-        info.avatar_url = j.at("avatar_url");
-}
-
-struct RoomSearchResult
-{
-        std::string room_id;
-        RoomInfo info;
-        QImage img;
-};
-
-Q_DECLARE_METATYPE(RoomSearchResult)
-Q_DECLARE_METATYPE(RoomInfo)
-
-// Extra information associated with an outbound megolm session.
-struct OutboundGroupSessionData
-{
-        std::string session_id;
-        std::string session_key;
-        uint64_t message_index = 0;
-};
-
-inline void
-to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
-{
-        obj["session_id"]    = msg.session_id;
-        obj["session_key"]   = msg.session_key;
-        obj["message_index"] = msg.message_index;
-}
-
-inline void
-from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
-{
-        msg.session_id    = obj.at("session_id");
-        msg.session_key   = obj.at("session_key");
-        msg.message_index = obj.at("message_index");
-}
-
-struct OutboundGroupSessionDataRef
-{
-        OlmOutboundGroupSession *session;
-        OutboundGroupSessionData data;
-};
-
-struct DevicePublicKeys
-{
-        std::string ed25519;
-        std::string curve25519;
-};
-
-inline void
-to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
-{
-        obj["ed25519"]    = msg.ed25519;
-        obj["curve25519"] = msg.curve25519;
-}
-
-inline void
-from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
-{
-        msg.ed25519    = obj.at("ed25519");
-        msg.curve25519 = obj.at("curve25519");
-}
-
-//! Represents a unique megolm session identifier.
-struct MegolmSessionIndex
-{
-        //! The room in which this session exists.
-        std::string room_id;
-        //! The session_id of the megolm session.
-        std::string session_id;
-        //! The curve25519 public key of the sender.
-        std::string sender_key;
-};
-
-inline void
-to_json(nlohmann::json &obj, const MegolmSessionIndex &msg)
-{
-        obj["room_id"]    = msg.room_id;
-        obj["session_id"] = msg.session_id;
-        obj["sender_key"] = msg.sender_key;
-}
-
-inline void
-from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
-{
-        msg.room_id    = obj.at("room_id");
-        msg.session_id = obj.at("session_id");
-        msg.sender_key = obj.at("sender_key");
-}
-
-struct OlmSessionStorage
-{
-        // Megolm sessions
-        std::map group_inbound_sessions;
-        std::map group_outbound_sessions;
-        std::map group_outbound_session_data;
-
-        // Guards for accessing megolm sessions.
-        std::mutex group_outbound_mtx;
-        std::mutex group_inbound_mtx;
-};
-
-class Cache : public QObject
-{
-        Q_OBJECT
-
-public:
-        Cache(const QString &userId, QObject *parent = nullptr);
-
-        static QHash DisplayNames;
-        static QHash AvatarUrls;
-        static QHash UserColors;
-
-        static std::string displayName(const std::string &room_id, const std::string &user_id);
-        static QString displayName(const QString &room_id, const QString &user_id);
-        static QString avatarUrl(const QString &room_id, const QString &user_id);
-        static QString userColor(const QString &user_id);
-
-        static void removeDisplayName(const QString &room_id, const QString &user_id);
-        static void removeAvatarUrl(const QString &room_id, const QString &user_id);
-        static void removeUserColor(const QString &user_id);
-
-        static void insertDisplayName(const QString &room_id,
-                                      const QString &user_id,
-                                      const QString &display_name);
-        static void insertAvatarUrl(const QString &room_id,
-                                    const QString &user_id,
-                                    const QString &avatar_url);
-        static void insertUserColor(const QString &user_id, const QString &color_name);
-
-        static void clearUserColors();
-
-        //! Load saved data for the display names & avatars.
-        void populateMembers();
-        std::vector joinedRooms();
-
-        QMap roomInfo(bool withInvites = true);
-        std::map invites();
-
-        //! Calculate & return the name of the room.
-        QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        //! Get room join rules
-        JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
-        bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
-        //! Retrieve the topic of the room if any.
-        QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
-        //! Retrieve the room avatar's url if any.
-        QString getRoomAvatarUrl(lmdb::txn &txn,
-                                 lmdb::dbi &statesdb,
-                                 lmdb::dbi &membersdb,
-                                 const QString &room_id);
-
-        //! Retrieve member info from a room.
-        std::vector getMembers(const std::string &room_id,
-                                           std::size_t startIndex = 0,
-                                           std::size_t len        = 30);
-
-        void saveState(const mtx::responses::Sync &res);
-        bool isInitialized() const;
-
-        std::string nextBatchToken() const;
-
-        void deleteData();
-
-        void removeInvite(lmdb::txn &txn, const std::string &room_id);
-        void removeInvite(const std::string &room_id);
-        void removeRoom(lmdb::txn &txn, const std::string &roomid);
-        void removeRoom(const std::string &roomid);
-        void removeRoom(const QString &roomid) { removeRoom(roomid.toStdString()); };
-        void setup();
-
-        bool isFormatValid();
-        void setCurrentFormat();
-
-        std::map roomMessages();
-
-        //! Retrieve all the user ids from a room.
-        std::vector roomMembers(const std::string &room_id);
-
-        //! Check if the given user has power leve greater than than
-        //! lowest power level of the given events.
-        bool hasEnoughPowerLevel(const std::vector &eventTypes,
-                                 const std::string &room_id,
-                                 const std::string &user_id);
-
-        //! Retrieves the saved room avatar.
-        QImage getRoomAvatar(const QString &id);
-        QImage getRoomAvatar(const std::string &id);
-
-        //! Adds a user to the read list for the given event.
-        //!
-        //! There should be only one user id present in a receipt list per room.
-        //! The user id should be removed from any other lists.
-        using Receipts = std::map>;
-        void updateReadReceipt(lmdb::txn &txn,
-                               const std::string &room_id,
-                               const Receipts &receipts);
-
-        //! Retrieve all the read receipts for the given event id and room.
-        //!
-        //! Returns a map of user ids and the time of the read receipt in milliseconds.
-        using UserReceipts = std::multimap>;
-        UserReceipts readReceipts(const QString &event_id, const QString &room_id);
-
-        //! Filter the events that have at least one read receipt.
-        std::vector filterReadEvents(const QString &room_id,
-                                              const std::vector &event_ids,
-                                              const std::string &excluded_user);
-        //! Add event for which we are expecting some read receipts.
-        void addPendingReceipt(const QString &room_id, const QString &event_id);
-        void removePendingReceipt(lmdb::txn &txn,
-                                  const std::string &room_id,
-                                  const std::string &event_id);
-        void notifyForReadReceipts(const std::string &room_id);
-        std::vector pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
-
-        QByteArray image(const QString &url) const;
-        QByteArray image(lmdb::txn &txn, const std::string &url) const;
-        QByteArray image(const std::string &url) const
-        {
-                return image(QString::fromStdString(url));
-        }
-        void saveImage(const std::string &url, const std::string &data);
-        void saveImage(const QString &url, const QByteArray &data);
-
-        RoomInfo singleRoomInfo(const std::string &room_id);
-        std::vector roomsWithStateUpdates(const mtx::responses::Sync &res);
-        std::vector roomsWithTagUpdates(const mtx::responses::Sync &res);
-        std::map getRoomInfo(const std::vector &rooms);
-        std::map roomUpdates(const mtx::responses::Sync &sync)
-        {
-                return getRoomInfo(roomsWithStateUpdates(sync));
-        }
-        std::map roomTagUpdates(const mtx::responses::Sync &sync)
-        {
-                return getRoomInfo(roomsWithTagUpdates(sync));
-        }
-
-        //! Calculates which the read status of a room.
-        //! Whether all the events in the timeline have been read.
-        bool calculateRoomReadStatus(const std::string &room_id);
-        void calculateRoomReadStatus();
-
-        QVector searchUsers(const std::string &room_id,
-                                          const std::string &query,
-                                          std::uint8_t max_items = 5);
-        std::vector searchRooms(const std::string &query,
-                                                  std::uint8_t max_items = 5);
-
-        void markSentNotification(const std::string &event_id);
-        //! Removes an event from the sent notifications.
-        void removeReadNotification(const std::string &event_id);
-        //! Check if we have sent a desktop notification for the given event id.
-        bool isNotificationSent(const std::string &event_id);
-
-        //! Remove old unused data.
-        void deleteOldMessages();
-        void deleteOldData() noexcept;
-        //! Retrieve all saved room ids.
-        std::vector getRoomIds(lmdb::txn &txn);
-
-        //! Mark a room that uses e2e encryption.
-        void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
-        bool isRoomEncrypted(const std::string &room_id);
-
-        //! Save the public keys for a device.
-        void saveDeviceKeys(const std::string &device_id);
-        void getDeviceKeys(const std::string &device_id);
-
-        //! Save the device list for a user.
-        void setDeviceList(const std::string &user_id, const std::vector &devices);
-        std::vector getDeviceList(const std::string &user_id);
-
-        //! Check if a user is a member of the room.
-        bool isRoomMember(const std::string &user_id, const std::string &room_id);
-
-        //
-        // Outbound Megolm Sessions
-        //
-        void saveOutboundMegolmSession(const std::string &room_id,
-                                       const OutboundGroupSessionData &data,
-                                       mtx::crypto::OutboundGroupSessionPtr session);
-        OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
-        bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
-        void updateOutboundMegolmSession(const std::string &room_id, int message_index);
-
-        void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
-        mtx::crypto::ExportedSessionKeys exportSessionKeys();
-
-        //
-        // Inbound Megolm Sessions
-        //
-        void saveInboundMegolmSession(const MegolmSessionIndex &index,
-                                      mtx::crypto::InboundGroupSessionPtr session);
-        OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
-        bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
-
-        //
-        // Olm Sessions
-        //
-        void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
-        std::vector getOlmSessions(const std::string &curve25519);
-        boost::optional getOlmSession(const std::string &curve25519,
-                                                                  const std::string &session_id);
-
-        void saveOlmAccount(const std::string &pickled);
-        std::string restoreOlmAccount();
-
-        void restoreSessions();
-
-        OlmSessionStorage session_storage;
-
-signals:
-        void newReadReceipts(const QString &room_id, const std::vector &event_ids);
-        void roomReadStatus(const std::map &status);
-
-private:
-        //! Save an invited room.
-        void saveInvite(lmdb::txn &txn,
-                        lmdb::dbi &statesdb,
-                        lmdb::dbi &membersdb,
-                        const mtx::responses::InvitedRoom &room);
-
-        QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
-        QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-
-        DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
-        void saveTimelineMessages(lmdb::txn &txn,
-                                  const std::string &room_id,
-                                  const mtx::responses::Timeline &res);
-
-        mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
-
-        //! Remove a room from the cache.
-        // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
-        template
-        void saveStateEvents(lmdb::txn &txn,
-                             const lmdb::dbi &statesdb,
-                             const lmdb::dbi &membersdb,
-                             const std::string &room_id,
-                             const std::vector &events)
-        {
-                for (const auto &e : events)
-                        saveStateEvent(txn, statesdb, membersdb, room_id, e);
-        }
-
-        template
-        void saveStateEvent(lmdb::txn &txn,
-                            const lmdb::dbi &statesdb,
-                            const lmdb::dbi &membersdb,
-                            const std::string &room_id,
-                            const T &event)
-        {
-                using namespace mtx::events;
-                using namespace mtx::events::state;
-
-                if (boost::get>(&event) != nullptr) {
-                        const auto e = boost::get>(event);
-
-                        switch (e.content.membership) {
-                        //
-                        // We only keep users with invite or join membership.
-                        //
-                        case Membership::Invite:
-                        case Membership::Join: {
-                                auto display_name = e.content.display_name.empty()
-                                                      ? e.state_key
-                                                      : e.content.display_name;
-
-                                // Lightweight representation of a member.
-                                MemberInfo tmp{display_name, e.content.avatar_url};
-
-                                lmdb::dbi_put(txn,
-                                              membersdb,
-                                              lmdb::val(e.state_key),
-                                              lmdb::val(json(tmp).dump()));
-
-                                insertDisplayName(QString::fromStdString(room_id),
-                                                  QString::fromStdString(e.state_key),
-                                                  QString::fromStdString(display_name));
-
-                                insertAvatarUrl(QString::fromStdString(room_id),
-                                                QString::fromStdString(e.state_key),
-                                                QString::fromStdString(e.content.avatar_url));
-
-                                break;
-                        }
-                        default: {
-                                lmdb::dbi_del(
-                                  txn, membersdb, lmdb::val(e.state_key), lmdb::val(""));
-
-                                removeDisplayName(QString::fromStdString(room_id),
-                                                  QString::fromStdString(e.state_key));
-                                removeAvatarUrl(QString::fromStdString(room_id),
-                                                QString::fromStdString(e.state_key));
-
-                                break;
-                        }
-                        }
-
-                        return;
-                } else if (boost::get>(&event) != nullptr) {
-                        setEncryptedRoom(txn, room_id);
-                        return;
-                }
-
-                if (!isStateEvent(event))
-                        return;
-
-                boost::apply_visitor(
-                  [&txn, &statesdb](auto e) {
-                          lmdb::dbi_put(
-                            txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
-                  },
-                  event);
-        }
-
-        template
-        bool isStateEvent(const T &e)
-        {
-                using namespace mtx::events;
-                using namespace mtx::events::state;
-
-                return boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr;
-        }
-
-        template
-        bool containsStateUpdates(const T &e)
-        {
-                using namespace mtx::events;
-                using namespace mtx::events::state;
-
-                return boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr;
-        }
-
-        bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
-        {
-                using namespace mtx::events;
-                using namespace mtx::events::state;
-
-                return boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr ||
-                       boost::get>(&e) != nullptr;
-        }
-
-        void saveInvites(lmdb::txn &txn,
-                         const std::map &rooms);
-
-        //! Sends signals for the rooms that are removed.
-        void removeLeftRooms(lmdb::txn &txn,
-                             const std::map &rooms)
-        {
-                for (const auto &room : rooms) {
-                        removeRoom(txn, room.first);
-
-                        // Clean up leftover invites.
-                        removeInvite(txn, room.first);
-                }
-        }
-
-        lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
-        {
-                return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
-        }
-
-        lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                auto db =
-                  lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
-                lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
-
-                return db;
-        }
-
-        lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
-        }
-
-        lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
-        }
-
-        lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
-        }
-
-        lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
-        }
-
-        //! Retrieves or creates the database that stores the open OLM sessions between our device
-        //! and the given curve25519 key which represents another device.
-        //!
-        //! Each entry is a map from the session_id to the pickled representation of the session.
-        lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string("olm_sessions/" + curve25519_key).c_str(), MDB_CREATE);
-        }
-
-        QString getDisplayName(const mtx::events::StateEvent &event)
-        {
-                if (!event.content.display_name.empty())
-                        return QString::fromStdString(event.content.display_name);
-
-                return QString::fromStdString(event.state_key);
-        }
-
-        void setNextBatchToken(lmdb::txn &txn, const std::string &token);
-        void setNextBatchToken(lmdb::txn &txn, const QString &token);
-
-        lmdb::env env_;
-        lmdb::dbi syncStateDb_;
-        lmdb::dbi roomsDb_;
-        lmdb::dbi invitesDb_;
-        lmdb::dbi mediaDb_;
-        lmdb::dbi readReceiptsDb_;
-        lmdb::dbi notificationsDb_;
-
-        lmdb::dbi devicesDb_;
-        lmdb::dbi deviceKeysDb_;
-
-        lmdb::dbi inboundMegolmSessionDb_;
-        lmdb::dbi outboundMegolmSessionDb_;
-
-        QString localUserId_;
-        QString cacheDirectory_;
-};
+#include "CacheCryptoStructs.h"
+#include "CacheStructs.h"
 
 namespace cache {
 void
 init(const QString &user_id);
 
-Cache *
-client();
+std::string
+displayName(const std::string &room_id, const std::string &user_id);
+QString
+displayName(const QString &room_id, const QString &user_id);
+QString
+avatarUrl(const QString &room_id, const QString &user_id);
+
+void
+removeDisplayName(const QString &room_id, const QString &user_id);
+void
+removeAvatarUrl(const QString &room_id, const QString &user_id);
+
+void
+insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name);
+void
+insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
+
+//! Load saved data for the display names & avatars.
+void
+populateMembers();
+std::vector
+joinedRooms();
+
+QMap
+roomInfo(bool withInvites = true);
+std::map
+invites();
+
+//! Calculate & return the name of the room.
+QString
+getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+//! Get room join rules
+mtx::events::state::JoinRule
+getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
+bool
+getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
+//! Retrieve the topic of the room if any.
+QString
+getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+//! Retrieve the room avatar's url if any.
+QString
+getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb, const QString &room_id);
+//! Retrieve the version of the room if any.
+QString
+getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
+
+//! Retrieve member info from a room.
+std::vector
+getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
+
+void
+saveState(const mtx::responses::Sync &res);
+bool
+isInitialized();
+
+std::string
+nextBatchToken();
+
+void
+deleteData();
+
+void
+removeInvite(lmdb::txn &txn, const std::string &room_id);
+void
+removeInvite(const std::string &room_id);
+void
+removeRoom(lmdb::txn &txn, const std::string &roomid);
+void
+removeRoom(const std::string &roomid);
+void
+removeRoom(const QString &roomid);
+void
+setup();
+
+bool
+isFormatValid();
+void
+setCurrentFormat();
+
+std::map
+roomMessages();
+
+QMap
+getTimelineMentions();
+
+//! Retrieve all the user ids from a room.
+std::vector
+roomMembers(const std::string &room_id);
+
+//! Check if the given user has power leve greater than than
+//! lowest power level of the given events.
+bool
+hasEnoughPowerLevel(const std::vector &eventTypes,
+                    const std::string &room_id,
+                    const std::string &user_id);
+
+//! Retrieves the saved room avatar.
+QImage
+getRoomAvatar(const QString &id);
+QImage
+getRoomAvatar(const std::string &id);
+
+//! Adds a user to the read list for the given event.
+//!
+//! There should be only one user id present in a receipt list per room.
+//! The user id should be removed from any other lists.
+using Receipts = std::map>;
+void
+updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts);
+
+//! Retrieve all the read receipts for the given event id and room.
+//!
+//! Returns a map of user ids and the time of the read receipt in milliseconds.
+using UserReceipts = std::multimap>;
+UserReceipts
+readReceipts(const QString &event_id, const QString &room_id);
+
+//! Filter the events that have at least one read receipt.
+std::vector
+filterReadEvents(const QString &room_id,
+                 const std::vector &event_ids,
+                 const std::string &excluded_user);
+//! Add event for which we are expecting some read receipts.
+void
+addPendingReceipt(const QString &room_id, const QString &event_id);
+void
+removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id);
+void
+notifyForReadReceipts(const std::string &room_id);
+std::vector
+pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
+
+QByteArray
+image(const QString &url);
+QByteArray
+image(lmdb::txn &txn, const std::string &url);
+inline QByteArray
+image(const std::string &url)
+{
+        return image(QString::fromStdString(url));
+}
+void
+saveImage(const std::string &url, const std::string &data);
+void
+saveImage(const QString &url, const QByteArray &data);
+
+RoomInfo
+singleRoomInfo(const std::string &room_id);
+std::vector
+roomsWithStateUpdates(const mtx::responses::Sync &res);
+std::vector
+roomsWithTagUpdates(const mtx::responses::Sync &res);
+std::map
+getRoomInfo(const std::vector &rooms);
+inline std::map
+roomUpdates(const mtx::responses::Sync &sync)
+{
+        return getRoomInfo(roomsWithStateUpdates(sync));
+}
+inline std::map
+roomTagUpdates(const mtx::responses::Sync &sync)
+{
+        return getRoomInfo(roomsWithTagUpdates(sync));
+}
+
+//! Calculates which the read status of a room.
+//! Whether all the events in the timeline have been read.
+bool
+calculateRoomReadStatus(const std::string &room_id);
+void
+calculateRoomReadStatus();
+
+std::vector
+searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items = 5);
+std::vector
+searchRooms(const std::string &query, std::uint8_t max_items = 5);
+
+void
+markSentNotification(const std::string &event_id);
+//! Removes an event from the sent notifications.
+void
+removeReadNotification(const std::string &event_id);
+//! Check if we have sent a desktop notification for the given event id.
+bool
+isNotificationSent(const std::string &event_id);
+
+//! Add all notifications containing a user mention to the db.
+void
+saveTimelineMentions(const mtx::responses::Notifications &res);
+
+//! Remove old unused data.
+void
+deleteOldMessages();
+void
+deleteOldData() noexcept;
+//! Retrieve all saved room ids.
+std::vector
+getRoomIds(lmdb::txn &txn);
+
+//! Mark a room that uses e2e encryption.
+void
+setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
+bool
+isRoomEncrypted(const std::string &room_id);
+
+//! Check if a user is a member of the room.
+bool
+isRoomMember(const std::string &user_id, const std::string &room_id);
+
+//
+// Outbound Megolm Sessions
+//
+void
+saveOutboundMegolmSession(const std::string &room_id,
+                          const OutboundGroupSessionData &data,
+                          mtx::crypto::OutboundGroupSessionPtr session);
+OutboundGroupSessionDataRef
+getOutboundMegolmSession(const std::string &room_id);
+bool
+outboundMegolmSessionExists(const std::string &room_id) noexcept;
+void
+updateOutboundMegolmSession(const std::string &room_id, int message_index);
+
+void
+importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
+mtx::crypto::ExportedSessionKeys
+exportSessionKeys();
+
+//
+// Inbound Megolm Sessions
+//
+void
+saveInboundMegolmSession(const MegolmSessionIndex &index,
+                         mtx::crypto::InboundGroupSessionPtr session);
+OlmInboundGroupSession *
+getInboundMegolmSession(const MegolmSessionIndex &index);
+bool
+inboundMegolmSessionExists(const MegolmSessionIndex &index);
+
+//
+// Olm Sessions
+//
+void
+saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+std::vector
+getOlmSessions(const std::string &curve25519);
+std::optional
+getOlmSession(const std::string &curve25519, const std::string &session_id);
+
+void
+saveOlmAccount(const std::string &pickled);
+std::string
+restoreOlmAccount();
+
+void
+restoreSessions();
 }
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
new file mode 100644
index 00000000..14c9c86b
--- /dev/null
+++ b/src/CacheCryptoStructs.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include 
+#include 
+
+//#include 
+
+#include 
+#include 
+
+// Extra information associated with an outbound megolm session.
+struct OutboundGroupSessionData
+{
+        std::string session_id;
+        std::string session_key;
+        uint64_t message_index = 0;
+};
+
+void
+to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg);
+void
+from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg);
+
+struct OutboundGroupSessionDataRef
+{
+        OlmOutboundGroupSession *session;
+        OutboundGroupSessionData data;
+};
+
+struct DevicePublicKeys
+{
+        std::string ed25519;
+        std::string curve25519;
+};
+
+void
+to_json(nlohmann::json &obj, const DevicePublicKeys &msg);
+void
+from_json(const nlohmann::json &obj, DevicePublicKeys &msg);
+
+//! Represents a unique megolm session identifier.
+struct MegolmSessionIndex
+{
+        //! The room in which this session exists.
+        std::string room_id;
+        //! The session_id of the megolm session.
+        std::string session_id;
+        //! The curve25519 public key of the sender.
+        std::string sender_key;
+};
+
+void
+to_json(nlohmann::json &obj, const MegolmSessionIndex &msg);
+void
+from_json(const nlohmann::json &obj, MegolmSessionIndex &msg);
+
+struct OlmSessionStorage
+{
+        // Megolm sessions
+        std::map group_inbound_sessions;
+        std::map group_outbound_sessions;
+        std::map group_outbound_session_data;
+
+        // Guards for accessing megolm sessions.
+        std::mutex group_outbound_mtx;
+        std::mutex group_inbound_mtx;
+};
diff --git a/src/CacheStructs.h b/src/CacheStructs.h
new file mode 100644
index 00000000..2051afc8
--- /dev/null
+++ b/src/CacheStructs.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+struct RoomMember
+{
+        QString user_id;
+        QString display_name;
+        QImage avatar;
+};
+
+struct SearchResult
+{
+        QString user_id;
+        QString display_name;
+};
+
+//! Used to uniquely identify a list of read receipts.
+struct ReadReceiptKey
+{
+        std::string event_id;
+        std::string room_id;
+};
+
+void
+to_json(nlohmann::json &j, const ReadReceiptKey &key);
+
+void
+from_json(const nlohmann::json &j, ReadReceiptKey &key);
+
+struct DescInfo
+{
+        QString event_id;
+        QString userid;
+        QString body;
+        QString timestamp;
+        QDateTime datetime;
+};
+
+//! UI info associated with a room.
+struct RoomInfo
+{
+        //! The calculated name of the room.
+        std::string name;
+        //! The topic of the room.
+        std::string topic;
+        //! The calculated avatar url of the room.
+        std::string avatar_url;
+        //! The calculated version of this room set at creation time.
+        std::string version;
+        //! Whether or not the room is an invite.
+        bool is_invite = false;
+        //! Total number of members in the room.
+        int16_t member_count = 0;
+        //! Who can access to the room.
+        mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
+        bool guest_access                      = false;
+        //! Metadata describing the last message in the timeline.
+        DescInfo msgInfo;
+        //! The list of tags associated with this room
+        std::vector tags;
+};
+
+void
+to_json(nlohmann::json &j, const RoomInfo &info);
+void
+from_json(const nlohmann::json &j, RoomInfo &info);
+
+//! Basic information per member;
+struct MemberInfo
+{
+        std::string name;
+        std::string avatar_url;
+};
+
+void
+to_json(nlohmann::json &j, const MemberInfo &info);
+void
+from_json(const nlohmann::json &j, MemberInfo &info);
+
+struct RoomSearchResult
+{
+        std::string room_id;
+        RoomInfo info;
+};
diff --git a/src/Cache_p.h b/src/Cache_p.h
new file mode 100644
index 00000000..14ceafe8
--- /dev/null
+++ b/src/Cache_p.h
@@ -0,0 +1,490 @@
+/*
+ * nheko Copyright (C) 2019  The nheko authors
+ * nheko Copyright (C) 2017  Konstantinos Sideris 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see .
+ */
+
+#pragma once
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#if __has_include()
+#include 
+#else
+#include 
+#endif
+#include 
+
+#include 
+#include 
+
+#include "CacheCryptoStructs.h"
+#include "CacheStructs.h"
+
+int
+numeric_key_comparison(const MDB_val *a, const MDB_val *b);
+
+class Cache : public QObject
+{
+        Q_OBJECT
+
+public:
+        Cache(const QString &userId, QObject *parent = nullptr);
+
+        static std::string displayName(const std::string &room_id, const std::string &user_id);
+        static QString displayName(const QString &room_id, const QString &user_id);
+        static QString avatarUrl(const QString &room_id, const QString &user_id);
+
+        static void removeDisplayName(const QString &room_id, const QString &user_id);
+        static void removeAvatarUrl(const QString &room_id, const QString &user_id);
+
+        static void insertDisplayName(const QString &room_id,
+                                      const QString &user_id,
+                                      const QString &display_name);
+        static void insertAvatarUrl(const QString &room_id,
+                                    const QString &user_id,
+                                    const QString &avatar_url);
+
+        //! Load saved data for the display names & avatars.
+        void populateMembers();
+        std::vector joinedRooms();
+
+        QMap roomInfo(bool withInvites = true);
+        std::map invites();
+
+        //! Calculate & return the name of the room.
+        QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+        //! Get room join rules
+        mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
+        bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
+        //! Retrieve the topic of the room if any.
+        QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+        //! Retrieve the room avatar's url if any.
+        QString getRoomAvatarUrl(lmdb::txn &txn,
+                                 lmdb::dbi &statesdb,
+                                 lmdb::dbi &membersdb,
+                                 const QString &room_id);
+        //! Retrieve the version of the room if any.
+        QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
+
+        //! Retrieve member info from a room.
+        std::vector getMembers(const std::string &room_id,
+                                           std::size_t startIndex = 0,
+                                           std::size_t len        = 30);
+
+        void saveState(const mtx::responses::Sync &res);
+        bool isInitialized() const;
+
+        std::string nextBatchToken() const;
+
+        void deleteData();
+
+        void removeInvite(lmdb::txn &txn, const std::string &room_id);
+        void removeInvite(const std::string &room_id);
+        void removeRoom(lmdb::txn &txn, const std::string &roomid);
+        void removeRoom(const std::string &roomid);
+        void setup();
+
+        bool isFormatValid();
+        void setCurrentFormat();
+
+        std::map roomMessages();
+
+        QMap getTimelineMentions();
+
+        //! Retrieve all the user ids from a room.
+        std::vector roomMembers(const std::string &room_id);
+
+        //! Check if the given user has power leve greater than than
+        //! lowest power level of the given events.
+        bool hasEnoughPowerLevel(const std::vector &eventTypes,
+                                 const std::string &room_id,
+                                 const std::string &user_id);
+
+        //! Retrieves the saved room avatar.
+        QImage getRoomAvatar(const QString &id);
+        QImage getRoomAvatar(const std::string &id);
+
+        //! Adds a user to the read list for the given event.
+        //!
+        //! There should be only one user id present in a receipt list per room.
+        //! The user id should be removed from any other lists.
+        using Receipts = std::map>;
+        void updateReadReceipt(lmdb::txn &txn,
+                               const std::string &room_id,
+                               const Receipts &receipts);
+
+        //! Retrieve all the read receipts for the given event id and room.
+        //!
+        //! Returns a map of user ids and the time of the read receipt in milliseconds.
+        using UserReceipts = std::multimap>;
+        UserReceipts readReceipts(const QString &event_id, const QString &room_id);
+
+        //! Filter the events that have at least one read receipt.
+        std::vector filterReadEvents(const QString &room_id,
+                                              const std::vector &event_ids,
+                                              const std::string &excluded_user);
+        //! Add event for which we are expecting some read receipts.
+        void addPendingReceipt(const QString &room_id, const QString &event_id);
+        void removePendingReceipt(lmdb::txn &txn,
+                                  const std::string &room_id,
+                                  const std::string &event_id);
+        void notifyForReadReceipts(const std::string &room_id);
+        std::vector pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
+
+        QByteArray image(const QString &url) const;
+        QByteArray image(lmdb::txn &txn, const std::string &url) const;
+        void saveImage(const std::string &url, const std::string &data);
+        void saveImage(const QString &url, const QByteArray &data);
+
+        RoomInfo singleRoomInfo(const std::string &room_id);
+        std::vector roomsWithStateUpdates(const mtx::responses::Sync &res);
+        std::vector roomsWithTagUpdates(const mtx::responses::Sync &res);
+        std::map getRoomInfo(const std::vector &rooms);
+
+        //! Calculates which the read status of a room.
+        //! Whether all the events in the timeline have been read.
+        bool calculateRoomReadStatus(const std::string &room_id);
+        void calculateRoomReadStatus();
+
+        std::vector searchUsers(const std::string &room_id,
+                                              const std::string &query,
+                                              std::uint8_t max_items = 5);
+        std::vector searchRooms(const std::string &query,
+                                                  std::uint8_t max_items = 5);
+
+        void markSentNotification(const std::string &event_id);
+        //! Removes an event from the sent notifications.
+        void removeReadNotification(const std::string &event_id);
+        //! Check if we have sent a desktop notification for the given event id.
+        bool isNotificationSent(const std::string &event_id);
+
+        //! Add all notifications containing a user mention to the db.
+        void saveTimelineMentions(const mtx::responses::Notifications &res);
+
+        //! Remove old unused data.
+        void deleteOldMessages();
+        void deleteOldData() noexcept;
+        //! Retrieve all saved room ids.
+        std::vector getRoomIds(lmdb::txn &txn);
+
+        //! Mark a room that uses e2e encryption.
+        void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
+        bool isRoomEncrypted(const std::string &room_id);
+
+        //! Check if a user is a member of the room.
+        bool isRoomMember(const std::string &user_id, const std::string &room_id);
+
+        //
+        // Outbound Megolm Sessions
+        //
+        void saveOutboundMegolmSession(const std::string &room_id,
+                                       const OutboundGroupSessionData &data,
+                                       mtx::crypto::OutboundGroupSessionPtr session);
+        OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
+        bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
+        void updateOutboundMegolmSession(const std::string &room_id, int message_index);
+
+        void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
+        mtx::crypto::ExportedSessionKeys exportSessionKeys();
+
+        //
+        // Inbound Megolm Sessions
+        //
+        void saveInboundMegolmSession(const MegolmSessionIndex &index,
+                                      mtx::crypto::InboundGroupSessionPtr session);
+        OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
+        bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
+
+        //
+        // Olm Sessions
+        //
+        void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+        std::vector getOlmSessions(const std::string &curve25519);
+        std::optional getOlmSession(const std::string &curve25519,
+                                                                const std::string &session_id);
+
+        void saveOlmAccount(const std::string &pickled);
+        std::string restoreOlmAccount();
+
+        void restoreSessions();
+
+signals:
+        void newReadReceipts(const QString &room_id, const std::vector &event_ids);
+        void roomReadStatus(const std::map &status);
+
+private:
+        //! Save an invited room.
+        void saveInvite(lmdb::txn &txn,
+                        lmdb::dbi &statesdb,
+                        lmdb::dbi &membersdb,
+                        const mtx::responses::InvitedRoom &room);
+
+        //! Add a notification containing a user mention to the db.
+        void saveTimelineMentions(lmdb::txn &txn,
+                                  const std::string &room_id,
+                                  const QList &res);
+
+        //! Get timeline items that a user was mentions in for a given room
+        mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn,
+                                                                 const std::string &room_id);
+
+        QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+        QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+        QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+
+        std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
+        DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
+        void saveTimelineMessages(lmdb::txn &txn,
+                                  const std::string &room_id,
+                                  const mtx::responses::Timeline &res);
+
+        mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
+
+        //! Remove a room from the cache.
+        // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
+        template
+        void saveStateEvents(lmdb::txn &txn,
+                             const lmdb::dbi &statesdb,
+                             const lmdb::dbi &membersdb,
+                             const std::string &room_id,
+                             const std::vector &events)
+        {
+                for (const auto &e : events)
+                        saveStateEvent(txn, statesdb, membersdb, room_id, e);
+        }
+
+        template
+        void saveStateEvent(lmdb::txn &txn,
+                            const lmdb::dbi &statesdb,
+                            const lmdb::dbi &membersdb,
+                            const std::string &room_id,
+                            const T &event)
+        {
+                using namespace mtx::events;
+                using namespace mtx::events::state;
+
+                if (auto e = std::get_if>(&event); e != nullptr) {
+                        switch (e->content.membership) {
+                        //
+                        // We only keep users with invite or join membership.
+                        //
+                        case Membership::Invite:
+                        case Membership::Join: {
+                                auto display_name = e->content.display_name.empty()
+                                                      ? e->state_key
+                                                      : e->content.display_name;
+
+                                // Lightweight representation of a member.
+                                MemberInfo tmp{display_name, e->content.avatar_url};
+
+                                lmdb::dbi_put(txn,
+                                              membersdb,
+                                              lmdb::val(e->state_key),
+                                              lmdb::val(json(tmp).dump()));
+
+                                insertDisplayName(QString::fromStdString(room_id),
+                                                  QString::fromStdString(e->state_key),
+                                                  QString::fromStdString(display_name));
+
+                                insertAvatarUrl(QString::fromStdString(room_id),
+                                                QString::fromStdString(e->state_key),
+                                                QString::fromStdString(e->content.avatar_url));
+
+                                break;
+                        }
+                        default: {
+                                lmdb::dbi_del(
+                                  txn, membersdb, lmdb::val(e->state_key), lmdb::val(""));
+
+                                removeDisplayName(QString::fromStdString(room_id),
+                                                  QString::fromStdString(e->state_key));
+                                removeAvatarUrl(QString::fromStdString(room_id),
+                                                QString::fromStdString(e->state_key));
+
+                                break;
+                        }
+                        }
+
+                        return;
+                } else if (std::holds_alternative>(event)) {
+                        setEncryptedRoom(txn, room_id);
+                        return;
+                }
+
+                if (!isStateEvent(event))
+                        return;
+
+                std::visit(
+                  [&txn, &statesdb](auto e) {
+                          lmdb::dbi_put(
+                            txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
+                  },
+                  event);
+        }
+
+        template
+        bool isStateEvent(const T &e)
+        {
+                using namespace mtx::events;
+                using namespace mtx::events::state;
+
+                return std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e);
+        }
+
+        template
+        bool containsStateUpdates(const T &e)
+        {
+                using namespace mtx::events;
+                using namespace mtx::events::state;
+
+                return std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e);
+        }
+
+        bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
+        {
+                using namespace mtx::events;
+                using namespace mtx::events::state;
+
+                return std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e) ||
+                       std::holds_alternative>(e);
+        }
+
+        void saveInvites(lmdb::txn &txn,
+                         const std::map &rooms);
+
+        //! Sends signals for the rooms that are removed.
+        void removeLeftRooms(lmdb::txn &txn,
+                             const std::map &rooms)
+        {
+                for (const auto &room : rooms) {
+                        removeRoom(txn, room.first);
+
+                        // Clean up leftover invites.
+                        removeInvite(txn, room.first);
+                }
+        }
+
+        lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
+        {
+                return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
+        }
+
+        lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                auto db =
+                  lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
+                lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
+
+                return db;
+        }
+
+        lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                return lmdb::dbi::open(
+                  txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
+        }
+
+        lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                return lmdb::dbi::open(
+                  txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
+        }
+
+        lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
+        }
+
+        lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
+        }
+
+        lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
+        }
+
+        //! Retrieves or creates the database that stores the open OLM sessions between our device
+        //! and the given curve25519 key which represents another device.
+        //!
+        //! Each entry is a map from the session_id to the pickled representation of the session.
+        lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
+        {
+                return lmdb::dbi::open(
+                  txn, std::string("olm_sessions/" + curve25519_key).c_str(), MDB_CREATE);
+        }
+
+        QString getDisplayName(const mtx::events::StateEvent &event)
+        {
+                if (!event.content.display_name.empty())
+                        return QString::fromStdString(event.content.display_name);
+
+                return QString::fromStdString(event.state_key);
+        }
+
+        void setNextBatchToken(lmdb::txn &txn, const std::string &token);
+        void setNextBatchToken(lmdb::txn &txn, const QString &token);
+
+        lmdb::env env_;
+        lmdb::dbi syncStateDb_;
+        lmdb::dbi roomsDb_;
+        lmdb::dbi invitesDb_;
+        lmdb::dbi mediaDb_;
+        lmdb::dbi readReceiptsDb_;
+        lmdb::dbi notificationsDb_;
+
+        lmdb::dbi devicesDb_;
+        lmdb::dbi deviceKeysDb_;
+
+        lmdb::dbi inboundMegolmSessionDb_;
+        lmdb::dbi outboundMegolmSessionDb_;
+
+        QString localUserId_;
+        QString cacheDirectory_;
+
+        static QHash DisplayNames;
+        static QHash AvatarUrls;
+
+        OlmSessionStorage session_storage;
+};
+
+namespace cache {
+Cache *
+client();
+}
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index dd23fb80..89bfd55a 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -18,10 +18,12 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 
 #include "AvatarProvider.h"
 #include "Cache.h"
+#include "Cache_p.h"
 #include "ChatPage.h"
 #include "Logging.h"
 #include "MainWindow.h"
@@ -33,7 +35,6 @@
 #include "Splitter.h"
 #include "TextInputWidget.h"
 #include "TopRoomBar.h"
-#include "TypingDisplay.h"
 #include "UserInfoWidget.h"
 #include "UserSettingsPage.h"
 #include "Utils.h"
@@ -43,6 +44,7 @@
 #include "notifications/Manager.h"
 
 #include "dialogs/ReadReceipts.h"
+#include "popups/UserMentions.h"
 #include "timeline/TimelineViewManager.h"
 
 // TODO: Needs to be updated with an actual secret.
@@ -53,6 +55,9 @@ constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000;
 constexpr int RETRY_TIMEOUT               = 5'000;
 constexpr size_t MAX_ONETIME_KEYS         = 50;
 
+Q_DECLARE_METATYPE(std::optional)
+Q_DECLARE_METATYPE(std::optional)
+
 ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
   : QWidget(parent)
   , isConnected_(true)
@@ -61,6 +66,9 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
 {
         setObjectName("chatPage");
 
+        qRegisterMetaType>();
+        qRegisterMetaType>();
+
         topLayout_ = new QHBoxLayout(this);
         topLayout_->setSpacing(0);
         topLayout_->setMargin(0);
@@ -76,7 +84,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         // SideBar
         sideBar_ = new QFrame(this);
         sideBar_->setObjectName("sideBar");
-        sideBar_->setMinimumWidth(utils::calculateSidebarSizes(QFont{}).normal);
+        sideBar_->setMinimumWidth(::splitter::calculateSidebarSizes(QFont{}).normal);
         sideBarLayout_ = new QVBoxLayout(sideBar_);
         sideBarLayout_->setSpacing(0);
         sideBarLayout_->setMargin(0);
@@ -88,8 +96,9 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(sidebarActions_, &SideBarActions::joinRoom, this, &ChatPage::joinRoom);
         connect(sidebarActions_, &SideBarActions::createRoom, this, &ChatPage::createRoom);
 
-        user_info_widget_ = new UserInfoWidget(sideBar_);
-        room_list_        = new RoomList(sideBar_);
+        user_info_widget_    = new UserInfoWidget(sideBar_);
+        user_mentions_popup_ = new popups::UserMentions();
+        room_list_           = new RoomList(sideBar_);
         connect(room_list_, &RoomList::joinRoom, this, &ChatPage::joinRoom);
 
         sideBarLayout_->addWidget(user_info_widget_);
@@ -108,15 +117,10 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         contentLayout_->setMargin(0);
 
         top_bar_      = new TopRoomBar(this);
-        view_manager_ = new TimelineViewManager(this);
+        view_manager_ = new TimelineViewManager(userSettings_, this);
 
         contentLayout_->addWidget(top_bar_);
-        contentLayout_->addWidget(view_manager_);
-
-        connect(this,
-                &ChatPage::removeTimelineEvent,
-                view_manager_,
-                &TimelineViewManager::removeTimelineEvent);
+        contentLayout_->addWidget(view_manager_->getWidget());
 
         // Splitter
         splitter->addWidget(sideBar_);
@@ -126,11 +130,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         text_input_ = new TextInputWidget(this);
         contentLayout_->addWidget(text_input_);
 
-        typingDisplay_ = new TypingDisplay(content_);
-        typingDisplay_->hide();
-        connect(
-          text_input_, &TextInputWidget::heightChanged, typingDisplay_, &TypingDisplay::setOffset);
-
         typingRefresher_ = new QTimer(this);
         typingRefresher_->setInterval(TYPING_REFRESH_TIMEOUT);
 
@@ -150,6 +149,41 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
                 trySync();
         });
 
+        connect(
+          new QShortcut(QKeySequence("Ctrl+Down"), this), &QShortcut::activated, this, [this]() {
+                  if (isVisible())
+                          room_list_->nextRoom();
+          });
+        connect(
+          new QShortcut(QKeySequence("Ctrl+Up"), this), &QShortcut::activated, this, [this]() {
+                  if (isVisible())
+                          room_list_->previousRoom();
+          });
+
+        connect(top_bar_, &TopRoomBar::mentionsClicked, this, [this](const QPoint &mentionsPos) {
+                if (user_mentions_popup_->isVisible()) {
+                        user_mentions_popup_->hide();
+                } else {
+                        showNotificationsDialog(mentionsPos);
+                        http::client()->notifications(
+                          1000,
+                          "",
+                          "highlight",
+                          [this, mentionsPos](const mtx::responses::Notifications &res,
+                                              mtx::http::RequestErr err) {
+                                  if (err) {
+                                          nhlog::net()->warn(
+                                            "failed to retrieve notifications: {} ({})",
+                                            err->matrix_error.error,
+                                            static_cast(err->status_code));
+                                          return;
+                                  }
+
+                                  emit highlightedNotifsRetrieved(std::move(res), mentionsPos);
+                          });
+                }
+        });
+
         connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL);
         connect(&connectivityTimer_, &QTimer::timeout, this, [=]() {
                 if (http::client()->access_token().empty()) {
@@ -186,30 +220,16 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
                                                mtx::http::RequestErr err) {
                                           if (err) {
                                                   emit showNotification(
-                                                    QString("Failed to invite user: %1").arg(user));
+                                                    tr("Failed to invite user: %1").arg(user));
                                                   return;
                                           }
 
-                                          emit showNotification(
-                                            QString("Invited user: %1").arg(user));
+                                          emit showNotification(tr("Invited user: %1").arg(user));
                                   });
                         });
                 }
         });
 
-        connect(room_list_, &RoomList::roomChanged, this, [this](const QString &roomid) {
-                QStringList users;
-
-                if (!userSettings_->isTypingNotificationsEnabled()) {
-                        typingDisplay_->setUsers(users);
-                        return;
-                }
-
-                if (typingUsers_.find(roomid) != typingUsers_.end())
-                        users = typingUsers_[roomid];
-
-                typingDisplay_->setUsers(users);
-        });
         connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping);
         connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
         connect(room_list_, &RoomList::roomChanged, splitter, &Splitter::showChatView);
@@ -260,22 +280,31 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
                 SLOT(showUnreadMessageNotification(int)));
 
         connect(text_input_,
-                SIGNAL(sendTextMessage(const QString &)),
+                &TextInputWidget::sendTextMessage,
                 view_manager_,
-                SLOT(queueTextMessage(const QString &)));
+                &TimelineViewManager::queueTextMessage);
 
         connect(text_input_,
-                SIGNAL(sendEmoteMessage(const QString &)),
+                &TextInputWidget::sendEmoteMessage,
                 view_manager_,
-                SLOT(queueEmoteMessage(const QString &)));
+                &TimelineViewManager::queueEmoteMessage);
 
         connect(text_input_, &TextInputWidget::sendJoinRoomRequest, this, &ChatPage::joinRoom);
 
+        // invites and bans via quick command
+        connect(text_input_, &TextInputWidget::sendInviteRoomRequest, this, &ChatPage::inviteUser);
+        connect(text_input_, &TextInputWidget::sendKickRoomRequest, this, &ChatPage::kickUser);
+        connect(text_input_, &TextInputWidget::sendBanRoomRequest, this, &ChatPage::banUser);
+        connect(text_input_, &TextInputWidget::sendUnbanRoomRequest, this, &ChatPage::unbanUser);
+
         connect(
           text_input_,
-          &TextInputWidget::uploadImage,
+          &TextInputWidget::uploadMedia,
           this,
-          [this](QSharedPointer dev, const QString &fn) {
+          [this](QSharedPointer dev,
+                 QString mimeClass,
+                 const QString &fn,
+                 const std::optional &related) {
                   QMimeDatabase db;
                   QMimeType mime = db.mimeTypeForData(dev.data());
 
@@ -285,205 +314,89 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
                           return;
                   }
 
-                  auto bin        = dev->peek(dev->size());
-                  auto payload    = std::string(bin.data(), bin.size());
-                  auto dimensions = QImageReader(dev.data()).size();
+                  auto bin     = dev->peek(dev->size());
+                  auto payload = std::string(bin.data(), bin.size());
+                  std::optional encryptedFile;
+                  if (cache::isRoomEncrypted(current_room_.toStdString())) {
+                          mtx::crypto::BinaryBuf buf;
+                          std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
+                          payload                      = mtx::crypto::to_string(buf);
+                  }
+
+                  QSize dimensions;
+                  if (mimeClass == "image")
+                          dimensions = QImageReader(dev.data()).size();
 
                   http::client()->upload(
                     payload,
-                    mime.name().toStdString(),
+                    encryptedFile ? "application/octet-stream" : mime.name().toStdString(),
                     QFileInfo(fn).fileName().toStdString(),
                     [this,
                      room_id  = current_room_,
                      filename = fn,
-                     mime     = mime.name(),
-                     size     = payload.size(),
-                     dimensions](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
+                     encryptedFile,
+                     mimeClass,
+                     mime = mime.name(),
+                     size = payload.size(),
+                     dimensions,
+                     related](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
                             if (err) {
                                     emit uploadFailed(
-                                      tr("Failed to upload image. Please try again."));
-                                    nhlog::net()->warn("failed to upload image: {} {} ({})",
+                                      tr("Failed to upload media. Please try again."));
+                                    nhlog::net()->warn("failed to upload media: {} {} ({})",
                                                        err->matrix_error.error,
                                                        to_string(err->matrix_error.errcode),
                                                        static_cast(err->status_code));
                                     return;
                             }
 
-                            emit imageUploaded(room_id,
+                            emit mediaUploaded(room_id,
                                                filename,
+                                               encryptedFile,
                                                QString::fromStdString(res.content_uri),
+                                               mimeClass,
                                                mime,
                                                size,
-                                               dimensions);
+                                               dimensions,
+                                               related);
                     });
           });
 
-        connect(text_input_,
-                &TextInputWidget::uploadFile,
-                this,
-                [this](QSharedPointer dev, const QString &fn) {
-                        QMimeDatabase db;
-                        QMimeType mime = db.mimeTypeForData(dev.data());
-
-                        if (!dev->open(QIODevice::ReadOnly)) {
-                                emit uploadFailed(
-                                  QString("Error while reading media: %1").arg(dev->errorString()));
-                                return;
-                        }
-
-                        auto bin     = dev->readAll();
-                        auto payload = std::string(bin.data(), bin.size());
-
-                        http::client()->upload(
-                          payload,
-                          mime.name().toStdString(),
-                          QFileInfo(fn).fileName().toStdString(),
-                          [this,
-                           room_id  = current_room_,
-                           filename = fn,
-                           mime     = mime.name(),
-                           size     = payload.size()](const mtx::responses::ContentURI &res,
-                                                  mtx::http::RequestErr err) {
-                                  if (err) {
-                                          emit uploadFailed(
-                                            tr("Failed to upload file. Please try again."));
-                                          nhlog::net()->warn("failed to upload file: {} ({})",
-                                                             err->matrix_error.error,
-                                                             static_cast(err->status_code));
-                                          return;
-                                  }
-
-                                  emit fileUploaded(room_id,
-                                                    filename,
-                                                    QString::fromStdString(res.content_uri),
-                                                    mime,
-                                                    size);
-                          });
-                });
-
-        connect(text_input_,
-                &TextInputWidget::uploadAudio,
-                this,
-                [this](QSharedPointer dev, const QString &fn) {
-                        QMimeDatabase db;
-                        QMimeType mime = db.mimeTypeForData(dev.data());
-
-                        if (!dev->open(QIODevice::ReadOnly)) {
-                                emit uploadFailed(
-                                  QString("Error while reading media: %1").arg(dev->errorString()));
-                                return;
-                        }
-
-                        auto bin     = dev->readAll();
-                        auto payload = std::string(bin.data(), bin.size());
-
-                        http::client()->upload(
-                          payload,
-                          mime.name().toStdString(),
-                          QFileInfo(fn).fileName().toStdString(),
-                          [this,
-                           room_id  = current_room_,
-                           filename = fn,
-                           mime     = mime.name(),
-                           size     = payload.size()](const mtx::responses::ContentURI &res,
-                                                  mtx::http::RequestErr err) {
-                                  if (err) {
-                                          emit uploadFailed(
-                                            tr("Failed to upload audio. Please try again."));
-                                          nhlog::net()->warn("failed to upload audio: {} ({})",
-                                                             err->matrix_error.error,
-                                                             static_cast(err->status_code));
-                                          return;
-                                  }
-
-                                  emit audioUploaded(room_id,
-                                                     filename,
-                                                     QString::fromStdString(res.content_uri),
-                                                     mime,
-                                                     size);
-                          });
-                });
-        connect(text_input_,
-                &TextInputWidget::uploadVideo,
-                this,
-                [this](QSharedPointer dev, const QString &fn) {
-                        QMimeDatabase db;
-                        QMimeType mime = db.mimeTypeForData(dev.data());
-
-                        if (!dev->open(QIODevice::ReadOnly)) {
-                                emit uploadFailed(
-                                  QString("Error while reading media: %1").arg(dev->errorString()));
-                                return;
-                        }
-
-                        auto bin     = dev->readAll();
-                        auto payload = std::string(bin.data(), bin.size());
-
-                        http::client()->upload(
-                          payload,
-                          mime.name().toStdString(),
-                          QFileInfo(fn).fileName().toStdString(),
-                          [this,
-                           room_id  = current_room_,
-                           filename = fn,
-                           mime     = mime.name(),
-                           size     = payload.size()](const mtx::responses::ContentURI &res,
-                                                  mtx::http::RequestErr err) {
-                                  if (err) {
-                                          emit uploadFailed(
-                                            tr("Failed to upload video. Please try again."));
-                                          nhlog::net()->warn("failed to upload video: {} ({})",
-                                                             err->matrix_error.error,
-                                                             static_cast(err->status_code));
-                                          return;
-                                  }
-
-                                  emit videoUploaded(room_id,
-                                                     filename,
-                                                     QString::fromStdString(res.content_uri),
-                                                     mime,
-                                                     size);
-                          });
-                });
-
         connect(this, &ChatPage::uploadFailed, this, [this](const QString &msg) {
                 text_input_->hideUploadSpinner();
                 emit showNotification(msg);
         });
-        connect(this,
-                &ChatPage::imageUploaded,
-                this,
-                [this](QString roomid,
-                       QString filename,
-                       QString url,
-                       QString mime,
-                       qint64 dsize,
-                       QSize dimensions) {
-                        text_input_->hideUploadSpinner();
-                        view_manager_->queueImageMessage(
-                          roomid, filename, url, mime, dsize, dimensions);
-                });
-        connect(this,
-                &ChatPage::fileUploaded,
-                this,
-                [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
-                        text_input_->hideUploadSpinner();
-                        view_manager_->queueFileMessage(roomid, filename, url, mime, dsize);
-                });
-        connect(this,
-                &ChatPage::audioUploaded,
-                this,
-                [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
-                        text_input_->hideUploadSpinner();
-                        view_manager_->queueAudioMessage(roomid, filename, url, mime, dsize);
-                });
-        connect(this,
-                &ChatPage::videoUploaded,
-                this,
-                [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
-                        text_input_->hideUploadSpinner();
-                        view_manager_->queueVideoMessage(roomid, filename, url, mime, dsize);
-                });
+        connect(
+          this,
+          &ChatPage::mediaUploaded,
+          this,
+          [this](QString roomid,
+                 QString filename,
+                 std::optional encryptedFile,
+                 QString url,
+                 QString mimeClass,
+                 QString mime,
+                 qint64 dsize,
+                 QSize dimensions,
+                 const std::optional &related) {
+                  text_input_->hideUploadSpinner();
+
+                  if (encryptedFile)
+                          encryptedFile->url = url.toStdString();
+
+                  if (mimeClass == "image")
+                          view_manager_->queueImageMessage(
+                            roomid, filename, encryptedFile, url, mime, dsize, dimensions, related);
+                  else if (mimeClass == "audio")
+                          view_manager_->queueAudioMessage(
+                            roomid, filename, encryptedFile, url, mime, dsize, related);
+                  else if (mimeClass == "video")
+                          view_manager_->queueVideoMessage(
+                            roomid, filename, encryptedFile, url, mime, dsize, related);
+                  else
+                          view_manager_->queueFileMessage(
+                            roomid, filename, encryptedFile, url, mime, dsize, related);
+          });
 
         connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
 
@@ -492,6 +405,16 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
 
         connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
         connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendDesktopNotifications);
+        connect(this,
+                &ChatPage::highlightedNotifsRetrieved,
+                this,
+                [](const mtx::responses::Notifications ¬if) {
+                        try {
+                                cache::saveTimelineMentions(notif);
+                        } catch (const lmdb::error &e) {
+                                nhlog::db()->error("failed to save mentions: {}", e.what());
+                        }
+                });
 
         connect(communitiesList_,
                 &CommunitiesList::communityChanged,
@@ -525,26 +448,28 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(this,
                 &ChatPage::initializeViews,
                 view_manager_,
-                [this](const mtx::responses::Rooms &rooms) { view_manager_->initialize(rooms); });
+                [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); });
         connect(this,
                 &ChatPage::initializeEmptyViews,
                 view_manager_,
                 &TimelineViewManager::initWithMessages);
+        connect(this,
+                &ChatPage::initializeMentions,
+                user_mentions_popup_,
+                &popups::UserMentions::initializeMentions);
         connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
                 try {
-                        room_list_->cleanupInvites(cache::client()->invites());
+                        room_list_->cleanupInvites(cache::invites());
                 } catch (const lmdb::error &e) {
                         nhlog::db()->error("failed to retrieve invites: {}", e.what());
                 }
 
-                view_manager_->initialize(rooms);
+                view_manager_->sync(rooms);
                 removeLeftRooms(rooms.leave);
 
                 bool hasNotifications = false;
                 for (const auto &room : rooms.join) {
                         auto room_id = QString::fromStdString(room.first);
-
-                        updateTypingUsers(room_id, room.second.ephemeral.typing);
                         updateRoomNotificationCount(
                           room_id,
                           room.second.unread_notifications.notification_count,
@@ -557,6 +482,8 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
                 if (hasNotifications && userSettings_->hasDesktopNotifications())
                         http::client()->notifications(
                           5,
+                          "",
+                          "",
                           [this](const mtx::responses::Notifications &res,
                                  mtx::http::RequestErr err) {
                                   if (err) {
@@ -594,6 +521,13 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
 
         connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
         connect(this, &ChatPage::messageReply, text_input_, &TextInputWidget::addReply);
+        connect(this, &ChatPage::messageReply, this, [this](const RelatedInfo &related) {
+                view_manager_->updateReplyingEvent(QString::fromStdString(related.related_event));
+        });
+        connect(view_manager_,
+                &TimelineViewManager::replyClosed,
+                text_input_,
+                &TextInputWidget::closeReplyPopup);
 
         instance_ = this;
 }
@@ -648,7 +582,7 @@ ChatPage::deleteConfigs()
         settings.remove("");
         settings.endGroup();
 
-        cache::client()->deleteData();
+        cache::deleteData();
         http::client()->clear();
 }
 
@@ -683,18 +617,18 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
                 connect(
                   cache::client(), &Cache::roomReadStatus, room_list_, &RoomList::updateReadStatus);
 
-                const bool isInitialized = cache::client()->isInitialized();
-                const bool isValid       = cache::client()->isFormatValid();
+                const bool isInitialized = cache::isInitialized();
+                const bool isValid       = cache::isFormatValid();
 
                 if (!isInitialized) {
-                        cache::client()->setCurrentFormat();
+                        cache::setCurrentFormat();
                 } else if (isInitialized && !isValid) {
                         // TODO: Deleting session data but keep using the
                         //	 same device doesn't work.
-                        cache::client()->deleteData();
+                        cache::deleteData();
 
                         cache::init(userid);
-                        cache::client()->setCurrentFormat();
+                        cache::setCurrentFormat();
                 } else if (isInitialized) {
                         loadStateFromCache();
                         return;
@@ -702,7 +636,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
 
         } catch (const lmdb::error &e) {
                 nhlog::db()->critical("failure during boot: {}", e.what());
-                cache::client()->deleteData();
+                cache::deleteData();
                 nhlog::net()->info("falling back to initial sync");
         }
 
@@ -711,7 +645,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
                 // There isn't a saved olm account to restore.
                 nhlog::crypto()->info("creating new olm account");
                 olm::client()->create_new_account();
-                cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
+                cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
         } catch (const lmdb::error &e) {
                 nhlog::crypto()->critical("failed to save olm account {}", e.what());
                 emit dropToLoginPageCb(QString::fromStdString(e.what()));
@@ -727,12 +661,12 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
 }
 
 void
-ChatPage::updateTopBarAvatar(const QString &roomid, const QPixmap &img)
+ChatPage::updateTopBarAvatar(const QString &roomid, const QString &img)
 {
         if (current_room_ != roomid)
                 return;
 
-        top_bar_->updateRoomAvatar(img.toImage());
+        top_bar_->updateRoomAvatar(img);
 }
 
 void
@@ -744,7 +678,7 @@ ChatPage::changeTopRoomInfo(const QString &room_id)
         }
 
         try {
-                auto room_info = cache::client()->getRoomInfo({room_id.toStdString()});
+                auto room_info = cache::getRoomInfo({room_id.toStdString()});
 
                 if (room_info.find(room_id) == room_info.end())
                         return;
@@ -755,12 +689,12 @@ ChatPage::changeTopRoomInfo(const QString &room_id)
                 top_bar_->updateRoomName(name);
                 top_bar_->updateRoomTopic(QString::fromStdString(room_info[room_id].topic));
 
-                auto img = cache::client()->getRoomAvatar(room_id);
+                auto img = cache::getRoomAvatar(room_id);
 
                 if (img.isNull())
                         top_bar_->updateRoomAvatarFromName(name);
                 else
-                        top_bar_->updateRoomAvatar(img);
+                        top_bar_->updateRoomAvatar(avatar_url);
 
         } catch (const lmdb::error &e) {
                 nhlog::ui()->error("failed to change top bar room info: {}", e.what());
@@ -792,17 +726,17 @@ ChatPage::loadStateFromCache()
 
         QtConcurrent::run([this]() {
                 try {
-                        cache::client()->restoreSessions();
-                        olm::client()->load(cache::client()->restoreOlmAccount(),
-                                            STORAGE_SECRET_KEY);
+                        cache::restoreSessions();
+                        olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY);
 
-                        cache::client()->populateMembers();
+                        cache::populateMembers();
 
-                        emit initializeEmptyViews(cache::client()->roomMessages());
-                        emit initializeRoomList(cache::client()->roomInfo());
-                        emit syncTags(cache::client()->roomInfo().toStdMap());
+                        emit initializeEmptyViews(cache::roomMessages());
+                        emit initializeRoomList(cache::roomInfo());
+                        emit initializeMentions(cache::getTimelineMentions());
+                        emit syncTags(cache::roomInfo().toStdMap());
 
-                        cache::client()->calculateRoomReadStatus();
+                        cache::calculateRoomReadStatus();
 
                 } catch (const mtx::crypto::olm_exception &e) {
                         nhlog::crypto()->critical("failed to restore olm account: {}", e.what());
@@ -845,8 +779,8 @@ void
 ChatPage::removeRoom(const QString &room_id)
 {
         try {
-                cache::client()->removeRoom(room_id);
-                cache::client()->removeInvite(room_id.toStdString());
+                cache::removeRoom(room_id);
+                cache::removeInvite(room_id.toStdString());
         } catch (const lmdb::error &e) {
                 nhlog::db()->critical("failure while removing room: {}", e.what());
                 // TODO: Notify the user.
@@ -855,38 +789,6 @@ ChatPage::removeRoom(const QString &room_id)
         room_list_->removeRoom(room_id, room_id == current_room_);
 }
 
-void
-ChatPage::updateTypingUsers(const QString &roomid, const std::vector &user_ids)
-{
-        if (!userSettings_->isTypingNotificationsEnabled())
-                return;
-
-        typingUsers_[roomid] = generateTypingUsers(roomid, user_ids);
-
-        if (current_room_ == roomid)
-                typingDisplay_->setUsers(typingUsers_[roomid]);
-}
-
-QStringList
-ChatPage::generateTypingUsers(const QString &room_id, const std::vector &typing_users)
-{
-        QStringList users;
-        auto local_user = utils::localUser();
-
-        for (const auto &uid : typing_users) {
-                const auto remote_user = QString::fromStdString(uid);
-
-                if (remote_user == local_user)
-                        continue;
-
-                users.append(Cache::displayName(room_id, remote_user));
-        }
-
-        users.sort();
-
-        return users;
-}
-
 void
 ChatPage::removeLeftRooms(const std::map &rooms)
 {
@@ -925,16 +827,16 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
 
                 try {
                         if (item.read) {
-                                cache::client()->removeReadNotification(event_id);
+                                cache::removeReadNotification(event_id);
                                 continue;
                         }
 
-                        if (!cache::client()->isNotificationSent(event_id)) {
+                        if (!cache::isNotificationSent(event_id)) {
                                 const auto room_id = QString::fromStdString(item.room_id);
                                 const auto user_id = utils::event_sender(item.event);
 
                                 // We should only sent one notification per event.
-                                cache::client()->markSentNotification(event_id);
+                                cache::markSentNotification(event_id);
 
                                 // Don't send a notification when the current room is opened.
                                 if (isRoomActive(room_id))
@@ -943,11 +845,10 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
                                 notificationsManager.postNotification(
                                   room_id,
                                   QString::fromStdString(event_id),
-                                  QString::fromStdString(
-                                    cache::client()->singleRoomInfo(item.room_id).name),
-                                  Cache::displayName(room_id, user_id),
+                                  QString::fromStdString(cache::singleRoomInfo(item.room_id).name),
+                                  cache::displayName(room_id, user_id),
                                   utils::event_body(item.event),
-                                  cache::client()->getRoomAvatar(room_id));
+                                  cache::getRoomAvatar(room_id));
                         }
                 } catch (const lmdb::error &e) {
                         nhlog::db()->warn("error while sending desktop notification: {}", e.what());
@@ -955,6 +856,18 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
         }
 }
 
+void
+ChatPage::showNotificationsDialog(const QPoint &widgetPos)
+{
+        auto notifDialog = user_mentions_popup_;
+
+        notifDialog->setGeometry(
+          widgetPos.x() - (width() / 10), widgetPos.y() + 25, width() / 5, height() / 2);
+
+        notifDialog->raise();
+        notifDialog->showPopup();
+}
+
 void
 ChatPage::tryInitialSync()
 {
@@ -1022,7 +935,7 @@ ChatPage::trySync()
                 connectivityTimer_.start();
 
         try {
-                opts.since = cache::client()->nextBatchToken();
+                opts.since = cache::nextBatchToken();
         } catch (const lmdb::error &e) {
                 nhlog::db()->error("failed to retrieve next batch token: {}", e.what());
                 return;
@@ -1075,22 +988,22 @@ ChatPage::trySync()
 
                   // TODO: fine grained error handling
                   try {
-                          cache::client()->saveState(res);
+                          cache::saveState(res);
                           olm::handle_to_device_messages(res.to_device);
 
                           emit syncUI(res.rooms);
 
-                          auto updates = cache::client()->roomUpdates(res);
+                          auto updates = cache::roomUpdates(res);
 
                           emit syncTopBar(updates);
                           emit syncRoomlist(updates);
 
-                          emit syncTags(cache::client()->roomTagUpdates(res));
+                          emit syncTags(cache::roomTagUpdates(res));
 
-                          cache::client()->deleteOldData();
+                          cache::deleteOldData();
                   } catch (const lmdb::map_full_error &e) {
                           nhlog::db()->error("lmdb is full: {}", e.what());
-                          cache::client()->deleteOldData();
+                          cache::deleteOldData();
                   } catch (const lmdb::error &e) {
                           nhlog::db()->error("saving sync response: {}", e.what());
                   }
@@ -1109,19 +1022,18 @@ ChatPage::joinRoom(const QString &room)
           room_id, [this, room_id](const nlohmann::json &, mtx::http::RequestErr err) {
                   if (err) {
                           emit showNotification(
-                            QString("Failed to join room: %1")
+                            tr("Failed to join room: %1")
                               .arg(QString::fromStdString(err->matrix_error.error)));
                           return;
                   }
 
-                  emit showNotification("You joined the room");
+                  emit tr("You joined the room");
 
                   // We remove any invites with the same room_id.
                   try {
-                          cache::client()->removeInvite(room_id);
+                          cache::removeInvite(room_id);
                   } catch (const lmdb::error &e) {
-                          emit showNotification(
-                            QString("Failed to remove invite: %1").arg(e.what()));
+                          emit showNotification(tr("Failed to remove invite: %1").arg(e.what()));
                   }
           });
 }
@@ -1144,8 +1056,8 @@ ChatPage::createRoom(const mtx::requests::CreateRoom &req)
                           return;
                   }
 
-                  emit showNotification(QString("Room %1 created")
-                                          .arg(QString::fromStdString(res.room_id.to_string())));
+                  emit showNotification(
+                    tr("Room %1 created").arg(QString::fromStdString(res.room_id.to_string())));
           });
 }
 
@@ -1165,6 +1077,83 @@ ChatPage::leaveRoom(const QString &room_id)
           });
 }
 
+void
+ChatPage::inviteUser(QString userid, QString reason)
+{
+        http::client()->invite_user(
+          current_room_.toStdString(),
+          userid.toStdString(),
+          [this, userid, room = current_room_](const mtx::responses::Empty &,
+                                               mtx::http::RequestErr err) {
+                  if (err) {
+                          emit showNotification(
+                            tr("Failed to invite %1 to %2: %3")
+                              .arg(userid)
+                              .arg(room)
+                              .arg(QString::fromStdString(err->matrix_error.error)));
+                  } else
+                          emit showNotification(tr("Invited user: %1").arg(userid));
+          },
+          reason.trimmed().toStdString());
+}
+void
+ChatPage::kickUser(QString userid, QString reason)
+{
+        http::client()->kick_user(
+          current_room_.toStdString(),
+          userid.toStdString(),
+          [this, userid, room = current_room_](const mtx::responses::Empty &,
+                                               mtx::http::RequestErr err) {
+                  if (err) {
+                          emit showNotification(
+                            tr("Failed to kick %1 to %2: %3")
+                              .arg(userid)
+                              .arg(room)
+                              .arg(QString::fromStdString(err->matrix_error.error)));
+                  } else
+                          emit showNotification(tr("Kicked user: %1").arg(userid));
+          },
+          reason.trimmed().toStdString());
+}
+void
+ChatPage::banUser(QString userid, QString reason)
+{
+        http::client()->ban_user(
+          current_room_.toStdString(),
+          userid.toStdString(),
+          [this, userid, room = current_room_](const mtx::responses::Empty &,
+                                               mtx::http::RequestErr err) {
+                  if (err) {
+                          emit showNotification(
+                            tr("Failed to ban %1 in %2: %3")
+                              .arg(userid)
+                              .arg(room)
+                              .arg(QString::fromStdString(err->matrix_error.error)));
+                  } else
+                          emit showNotification(tr("Banned user: %1").arg(userid));
+          },
+          reason.trimmed().toStdString());
+}
+void
+ChatPage::unbanUser(QString userid, QString reason)
+{
+        http::client()->unban_user(
+          current_room_.toStdString(),
+          userid.toStdString(),
+          [this, userid, room = current_room_](const mtx::responses::Empty &,
+                                               mtx::http::RequestErr err) {
+                  if (err) {
+                          emit showNotification(
+                            tr("Failed to unban %1 in %2: %3")
+                              .arg(userid)
+                              .arg(room)
+                              .arg(QString::fromStdString(err->matrix_error.error)));
+                  } else
+                          emit showNotification(tr("Unbanned user: %1").arg(userid));
+          },
+          reason.trimmed().toStdString());
+}
+
 void
 ChatPage::sendTypingNotifications()
 {
@@ -1183,6 +1172,8 @@ ChatPage::sendTypingNotifications()
 void
 ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err)
 {
+        // TODO: Initial Sync should include mentions as well...
+
         if (err) {
                 const auto error      = QString::fromStdString(err->matrix_error.error);
                 const auto msg        = tr("Please try to login again: %1").arg(error);
@@ -1214,15 +1205,16 @@ ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::Request
         nhlog::net()->info("initial sync completed");
 
         try {
-                cache::client()->saveState(res);
+                cache::saveState(res);
 
                 olm::handle_to_device_messages(res.to_device);
 
                 emit initializeViews(std::move(res.rooms));
-                emit initializeRoomList(cache::client()->roomInfo());
+                emit initializeRoomList(cache::roomInfo());
+                emit initializeMentions(cache::getTimelineMentions());
 
-                cache::client()->calculateRoomReadStatus();
-                emit syncTags(cache::client()->roomInfo().toStdMap());
+                cache::calculateRoomReadStatus();
+                emit syncTags(cache::roomInfo().toStdMap());
         } catch (const lmdb::error &e) {
                 nhlog::db()->error("failed to save state after initial sync: {}", e.what());
                 startInitialSync();
@@ -1274,37 +1266,7 @@ ChatPage::getProfileInfo()
 
                   emit setUserDisplayName(QString::fromStdString(res.display_name));
 
-                  if (cache::client()) {
-                          auto data = cache::client()->image(res.avatar_url);
-                          if (!data.isNull()) {
-                                  emit setUserAvatar(QImage::fromData(data));
-                                  return;
-                          }
-                  }
-
-                  if (res.avatar_url.empty())
-                          return;
-
-                  http::client()->download(
-                    res.avatar_url,
-                    [this, res](const std::string &data,
-                                const std::string &,
-                                const std::string &,
-                                mtx::http::RequestErr err) {
-                            if (err) {
-                                    nhlog::net()->warn(
-                                      "failed to download user avatar: {} - {}",
-                                      mtx::errors::to_string(err->matrix_error.errcode),
-                                      err->matrix_error.error);
-                                    return;
-                            }
-
-                            if (cache::client())
-                                    cache::client()->saveImage(res.avatar_url, data);
-
-                            emit setUserAvatar(
-                              QImage::fromData(QByteArray(data.data(), data.size())));
-                    });
+                  emit setUserAvatar(QString::fromStdString(res.avatar_url));
           });
 
         http::client()->joined_groups(
@@ -1349,7 +1311,7 @@ ChatPage::timelineWidth()
 bool
 ChatPage::isSideBarExpanded()
 {
-        const auto sz = utils::calculateSidebarSizes(QFont{});
+        const auto sz = splitter::calculateSidebarSizes(QFont{});
         return sideBar_->size().width() > sz.normal;
 }
 
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 7d3b3273..8e2e9192 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -18,19 +18,27 @@
 #pragma once
 
 #include 
-#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
 
 #include 
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
-#include "Cache.h"
+#include "CacheStructs.h"
 #include "CommunitiesList.h"
-#include "MatrixClient.h"
+#include "Utils.h"
 #include "notifications/Manager.h"
+#include "popups/UserMentions.h"
 
 class OverlayModal;
 class QuickSwitcher;
@@ -40,7 +48,6 @@ class Splitter;
 class TextInputWidget;
 class TimelineViewManager;
 class TopRoomBar;
-class TypingDisplay;
 class UserInfoWidget;
 class UserSettings;
 class NotificationsManager;
@@ -49,12 +56,16 @@ constexpr int CONSENSUS_TIMEOUT      = 1000;
 constexpr int SHOW_CONTENT_TIMEOUT   = 3000;
 constexpr int TYPING_REFRESH_TIMEOUT = 10000;
 
+namespace mtx::http {
+using RequestErr = const std::optional &;
+}
+
 class ChatPage : public QWidget
 {
         Q_OBJECT
 
 public:
-        ChatPage(QSharedPointer userSettings, QWidget *parent = 0);
+        ChatPage(QSharedPointer userSettings, QWidget *parent = nullptr);
 
         // Initialize all the components of the UI.
         void bootstrap(QString userid, QString homeserver, QString token);
@@ -79,36 +90,31 @@ public slots:
         void leaveRoom(const QString &room_id);
         void createRoom(const mtx::requests::CreateRoom &req);
 
+        void inviteUser(QString userid, QString reason);
+        void kickUser(QString userid, QString reason);
+        void banUser(QString userid, QString reason);
+        void unbanUser(QString userid, QString reason);
+
 signals:
         void connectionLost();
         void connectionRestored();
 
-        void messageReply(const QString &username, const QString &msg);
+        void messageReply(const RelatedInfo &related);
 
         void notificationsRetrieved(const mtx::responses::Notifications &);
+        void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
+                                        const QPoint widgetPos);
 
         void uploadFailed(const QString &msg);
-        void imageUploaded(const QString &roomid,
+        void mediaUploaded(const QString &roomid,
                            const QString &filename,
+                           const std::optional &file,
                            const QString &url,
+                           const QString &mimeClass,
                            const QString &mime,
                            qint64 dsize,
-                           const QSize &dimensions);
-        void fileUploaded(const QString &roomid,
-                          const QString &filename,
-                          const QString &url,
-                          const QString &mime,
-                          qint64 dsize);
-        void audioUploaded(const QString &roomid,
-                           const QString &filename,
-                           const QString &url,
-                           const QString &mime,
-                           qint64 dsize);
-        void videoUploaded(const QString &roomid,
-                           const QString &filename,
-                           const QString &url,
-                           const QString &mime,
-                           qint64 dsize);
+                           const QSize &dimensions,
+                           const std::optional &related);
 
         void contentLoaded();
         void closing();
@@ -119,11 +125,9 @@ signals:
         void showUserSettingsPage();
         void showOverlayProgressBar();
 
-        void removeTimelineEvent(const QString &room_id, const QString &event_id);
-
         void ownProfileOk();
         void setUserDisplayName(const QString &name);
-        void setUserAvatar(const QImage &avatar);
+        void setUserAvatar(const QString &avatar);
         void loggedOut();
 
         void trySyncCb();
@@ -134,6 +138,7 @@ signals:
         void initializeRoomList(QMap);
         void initializeViews(const mtx::responses::Rooms &rooms);
         void initializeEmptyViews(const std::map &msgs);
+        void initializeMentions(const QMap ¬ifs);
         void syncUI(const mtx::responses::Rooms &rooms);
         void syncRoomlist(const std::map &updates);
         void syncTags(const std::map &updates);
@@ -152,7 +157,7 @@ signals:
 
 private slots:
         void showUnreadMessageNotification(int count);
-        void updateTopBarAvatar(const QString &roomid, const QPixmap &img);
+        void updateTopBarAvatar(const QString &roomid, const QString &img);
         void changeTopRoomInfo(const QString &room_id);
         void logout();
         void removeRoom(const QString &room_id);
@@ -186,8 +191,6 @@ private:
         using LeftRooms = std::map;
         void removeLeftRooms(const LeftRooms &rooms);
 
-        void updateTypingUsers(const QString &roomid, const std::vector &user_ids);
-
         void loadStateFromCache();
         void resetUI();
         //! Decides whether or not to hide the group's sidebar.
@@ -203,8 +206,7 @@ private:
         //! Send desktop notification for the received messages.
         void sendDesktopNotifications(const mtx::responses::Notifications &);
 
-        QStringList generateTypingUsers(const QString &room_id,
-                                        const std::vector &typing_users);
+        void showNotificationsDialog(const QPoint &point);
 
         QHBoxLayout *topLayout_;
         Splitter *splitter;
@@ -225,7 +227,6 @@ private:
 
         TopRoomBar *top_bar_;
         TextInputWidget *text_input_;
-        TypingDisplay *typingDisplay_;
 
         QTimer connectivityTimer_;
         std::atomic_bool isConnected_;
@@ -235,8 +236,8 @@ private:
 
         UserInfoWidget *user_info_widget_;
 
-        // Keeps track of the users currently typing on each room.
-        std::map> typingUsers_;
+        popups::UserMentions *user_mentions_popup_;
+
         QTimer *typingRefresher_;
 
         // Global user settings.
@@ -254,9 +255,8 @@ ChatPage::getMemberships(const std::vector &collection) const
         using Member = mtx::events::StateEvent;
 
         for (const auto &event : collection) {
-                if (boost::get(event) != nullptr) {
-                        auto member = boost::get(event);
-                        memberships.emplace(member.state_key, member);
+                if (auto member = std::get_if(event)) {
+                        memberships.emplace(member->state_key, *member);
                 }
         }
 
diff --git a/src/ColorImageProvider.cpp b/src/ColorImageProvider.cpp
new file mode 100644
index 00000000..c580c394
--- /dev/null
+++ b/src/ColorImageProvider.cpp
@@ -0,0 +1,27 @@
+#include "ColorImageProvider.h"
+
+#include 
+
+QPixmap
+ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &)
+{
+        auto args = id.split('?');
+
+        QPixmap source(args[0]);
+
+        if (size)
+                *size = QSize(source.width(), source.height());
+
+        if (args.size() < 2)
+                return source;
+
+        QColor color(args[1]);
+
+        QPixmap colorized = source;
+        QPainter painter(&colorized);
+        painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
+        painter.fillRect(colorized.rect(), color);
+        painter.end();
+
+        return colorized;
+}
diff --git a/src/ColorImageProvider.h b/src/ColorImageProvider.h
new file mode 100644
index 00000000..21f36c12
--- /dev/null
+++ b/src/ColorImageProvider.h
@@ -0,0 +1,11 @@
+#include 
+
+class ColorImageProvider : public QQuickImageProvider
+{
+public:
+        ColorImageProvider()
+          : QQuickImageProvider(QQuickImageProvider::Pixmap)
+        {}
+
+        QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
+};
diff --git a/src/CommunitiesList.cpp b/src/CommunitiesList.cpp
index 6e46741b..bb57ca40 100644
--- a/src/CommunitiesList.cpp
+++ b/src/CommunitiesList.cpp
@@ -2,7 +2,9 @@
 #include "Cache.h"
 #include "Logging.h"
 #include "MatrixClient.h"
-#include "Utils.h"
+#include "Splitter.h"
+
+#include 
 
 #include 
 
@@ -14,13 +16,11 @@ CommunitiesList::CommunitiesList(QWidget *parent)
         sizePolicy.setVerticalStretch(1);
         setSizePolicy(sizePolicy);
 
-        setStyleSheet("border-style: none;");
-
         topLayout_ = new QVBoxLayout(this);
         topLayout_->setSpacing(0);
         topLayout_->setMargin(0);
 
-        const auto sideBarSizes = utils::calculateSidebarSizes(QFont{});
+        const auto sideBarSizes = splitter::calculateSidebarSizes(QFont{});
         setFixedWidth(sideBarSizes.groups);
 
         scrollArea_ = new QScrollArea(this);
@@ -30,16 +30,14 @@ CommunitiesList::CommunitiesList(QWidget *parent)
         scrollArea_->setWidgetResizable(true);
         scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter);
 
-        scrollAreaContents_ = new QWidget();
-
-        contentsLayout_ = new QVBoxLayout(scrollAreaContents_);
+        contentsLayout_ = new QVBoxLayout();
         contentsLayout_->setSpacing(0);
         contentsLayout_->setMargin(0);
 
         addGlobalItem();
         contentsLayout_->addStretch(1);
 
-        scrollArea_->setWidget(scrollAreaContents_);
+        scrollArea_->setLayout(contentsLayout_);
         topLayout_->addWidget(scrollArea_);
 
         connect(
@@ -185,7 +183,8 @@ void
 CommunitiesList::updateCommunityAvatar(const QString &community_id, const QPixmap &img)
 {
         if (!communityExists(community_id)) {
-                qWarning() << "Avatar update on nonexistent community" << community_id;
+                nhlog::ui()->warn("Avatar update on nonexistent community {}",
+                                  community_id.toStdString());
                 return;
         }
 
@@ -196,7 +195,7 @@ void
 CommunitiesList::highlightSelectedCommunity(const QString &community_id)
 {
         if (!communityExists(community_id)) {
-                qDebug() << "CommunitiesList: clicked unknown community";
+                nhlog::ui()->debug("CommunitiesList: clicked unknown community");
                 return;
         }
 
@@ -215,7 +214,7 @@ CommunitiesList::highlightSelectedCommunity(const QString &community_id)
 void
 CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUrl)
 {
-        auto savedImgData = cache::client()->image(avatarUrl);
+        auto savedImgData = cache::image(avatarUrl);
         if (!savedImgData.isNull()) {
                 QPixmap pix;
                 pix.loadFromData(savedImgData);
@@ -238,7 +237,7 @@ CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUr
                           return;
                   }
 
-                  cache::client()->saveImage(opts.mxc_url, res);
+                  cache::saveImage(opts.mxc_url, res);
 
                   auto data = QByteArray(res.data(), res.size());
 
diff --git a/src/CommunitiesList.h b/src/CommunitiesList.h
index b18df654..d3cbeeff 100644
--- a/src/CommunitiesList.h
+++ b/src/CommunitiesList.h
@@ -4,10 +4,15 @@
 #include 
 #include 
 
-#include "Cache.h"
+#include "CacheStructs.h"
 #include "CommunitiesListItem.h"
 #include "ui/Theme.h"
 
+namespace mtx::responses {
+struct GroupProfile;
+struct JoinedGroups;
+}
+
 class CommunitiesList : public QWidget
 {
         Q_OBJECT
@@ -48,7 +53,6 @@ private:
 
         QVBoxLayout *topLayout_;
         QVBoxLayout *contentsLayout_;
-        QWidget *scrollAreaContents_;
         QScrollArea *scrollArea_;
 
         std::map> communities_;
diff --git a/src/CommunitiesListItem.cpp b/src/CommunitiesListItem.cpp
index 324482d3..274271e5 100644
--- a/src/CommunitiesListItem.cpp
+++ b/src/CommunitiesListItem.cpp
@@ -1,4 +1,7 @@
 #include "CommunitiesListItem.h"
+
+#include 
+
 #include "Utils.h"
 #include "ui/Painter.h"
 #include "ui/Ripple.h"
diff --git a/src/CommunitiesListItem.h b/src/CommunitiesListItem.h
index d4d7e9c6..0cc5d60c 100644
--- a/src/CommunitiesListItem.h
+++ b/src/CommunitiesListItem.h
@@ -1,17 +1,14 @@
 #pragma once
 
-#include 
-#include 
-#include 
 #include 
 #include 
 
-#include 
-
 #include "Config.h"
 #include "ui/Theme.h"
 
 class RippleOverlay;
+class QPainter;
+class QMouseEvent;
 
 class CommunitiesListItem : public QWidget
 {
diff --git a/src/Config.h b/src/Config.h
index e1271452..f99cf36b 100644
--- a/src/Config.h
+++ b/src/Config.h
@@ -53,9 +53,9 @@ namespace strings {
 const QString url_html = "\\1";
 const QRegularExpression url_regex(
   // match an URL, that is not quoted, i.e.
-  // vvvvvvv match quote via negative lookahead/lookbehind                                 vvvvvv
-  //        vvvv atomic match url -> fail if there is a " before or after                vv
-  "(?((www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]\\)\\:]))(?!\")");
+  // vvvvvv match quote via negative lookahead/lookbehind                    vv
+  //       vvvv atomic match url -> fail if there is a " before or after          vvv
+  R"((?((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!"))");
 }
 
 // Window geometry.
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
new file mode 100644
index 00000000..20cdb63c
--- /dev/null
+++ b/src/EventAccessors.cpp
@@ -0,0 +1,383 @@
+#include "EventAccessors.h"
+
+#include 
+
+namespace {
+struct nonesuch
+{
+        ~nonesuch()                = delete;
+        nonesuch(nonesuch const &) = delete;
+        void operator=(nonesuch const &) = delete;
+};
+
+namespace detail {
+template class Op, class... Args>
+struct detector
+{
+        using value_t = std::false_type;
+        using type    = Default;
+};
+
+template class Op, class... Args>
+struct detector>, Op, Args...>
+{
+        using value_t = std::true_type;
+        using type    = Op;
+};
+
+} // namespace detail
+
+template class Op, class... Args>
+using is_detected = typename detail::detector::value_t;
+
+struct EventMsgType
+{
+        template
+        using msgtype_t = decltype(E::msgtype);
+        template
+        mtx::events::MessageType operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value)
+                        return mtx::events::getMessageType(e.content.msgtype);
+                return mtx::events::MessageType::Unknown;
+        }
+};
+
+struct EventRoomName
+{
+        template
+        std::string operator()(const T &e)
+        {
+                if constexpr (std::is_same_v, T>)
+                        return e.content.name;
+                return "";
+        }
+};
+
+struct EventRoomTopic
+{
+        template
+        std::string operator()(const T &e)
+        {
+                if constexpr (std::is_same_v, T>)
+                        return e.content.topic;
+                return "";
+        }
+};
+
+struct EventBody
+{
+        template
+        using body_t = decltype(C::body);
+        template
+        std::string operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value)
+                        return e.content.body;
+                return "";
+        }
+};
+
+struct EventFormattedBody
+{
+        template
+        using formatted_body_t = decltype(C::formatted_body);
+        template
+        std::string operator()(const mtx::events::RoomEvent &e)
+        {
+                if constexpr (is_detected::value)
+                        return e.content.formatted_body;
+                return "";
+        }
+};
+
+struct EventFile
+{
+        template
+        using file_t = decltype(Content::file);
+        template
+        std::optional operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value)
+                        return e.content.file;
+                return std::nullopt;
+        }
+};
+
+struct EventUrl
+{
+        template
+        using url_t = decltype(Content::url);
+        template
+        std::string operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value) {
+                        if (auto file = EventFile{}(e))
+                                return file->url;
+                        return e.content.url;
+                }
+                return "";
+        }
+};
+
+struct EventThumbnailUrl
+{
+        template
+        using thumbnail_url_t = decltype(Content::info.thumbnail_url);
+        template
+        std::string operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value) {
+                        return e.content.info.thumbnail_url;
+                }
+                return "";
+        }
+};
+
+struct EventFilename
+{
+        template
+        std::string operator()(const mtx::events::Event &)
+        {
+                return "";
+        }
+        std::string operator()(const mtx::events::RoomEvent &e)
+        {
+                // body may be the original filename
+                return e.content.body;
+        }
+        std::string operator()(const mtx::events::RoomEvent &e)
+        {
+                // body may be the original filename
+                return e.content.body;
+        }
+        std::string operator()(const mtx::events::RoomEvent &e)
+        {
+                // body may be the original filename
+                return e.content.body;
+        }
+        std::string operator()(const mtx::events::RoomEvent &e)
+        {
+                // body may be the original filename
+                if (!e.content.filename.empty())
+                        return e.content.filename;
+                return e.content.body;
+        }
+};
+
+struct EventMimeType
+{
+        template
+        using mimetype_t = decltype(Content::info.mimetype);
+        template
+        std::string operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value) {
+                        return e.content.info.mimetype;
+                }
+                return "";
+        }
+};
+
+struct EventFilesize
+{
+        template
+        using filesize_t = decltype(Content::info.size);
+        template
+        int64_t operator()(const mtx::events::RoomEvent &e)
+        {
+                if constexpr (is_detected::value) {
+                        return e.content.info.size;
+                }
+                return 0;
+        }
+};
+
+struct EventInReplyTo
+{
+        template
+        using related_ev_id_t = decltype(Content::relates_to.in_reply_to.event_id);
+        template
+        std::string operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value) {
+                        return e.content.relates_to.in_reply_to.event_id;
+                }
+                return "";
+        }
+};
+
+struct EventTransactionId
+{
+        template
+        std::string operator()(const mtx::events::RoomEvent &e)
+        {
+                return e.unsigned_data.transaction_id;
+        }
+        template
+        std::string operator()(const mtx::events::Event &e)
+        {
+                return e.unsigned_data.transaction_id;
+        }
+};
+
+struct EventMediaHeight
+{
+        template
+        using h_t = decltype(Content::info.h);
+        template
+        uint64_t operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value) {
+                        return e.content.info.h;
+                }
+                return -1;
+        }
+};
+
+struct EventMediaWidth
+{
+        template
+        using w_t = decltype(Content::info.w);
+        template
+        uint64_t operator()(const mtx::events::Event &e)
+        {
+                if constexpr (is_detected::value) {
+                        return e.content.info.w;
+                }
+                return -1;
+        }
+};
+
+template
+double
+eventPropHeight(const mtx::events::RoomEvent &e)
+{
+        auto w = eventWidth(e);
+        if (w == 0)
+                w = 1;
+
+        double prop = eventHeight(e) / (double)w;
+
+        return prop > 0 ? prop : 1.;
+}
+}
+
+std::string
+mtx::accessors::event_id(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit([](const auto e) { return e.event_id; }, event);
+}
+std::string
+mtx::accessors::room_id(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit([](const auto e) { return e.room_id; }, event);
+}
+
+std::string
+mtx::accessors::sender(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit([](const auto e) { return e.sender; }, event);
+}
+
+QDateTime
+mtx::accessors::origin_server_ts(const mtx::events::collections::TimelineEvents &event)
+{
+        return QDateTime::fromMSecsSinceEpoch(
+          std::visit([](const auto e) { return e.origin_server_ts; }, event));
+}
+
+std::string
+mtx::accessors::filename(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit(EventFilename{}, event);
+}
+
+mtx::events::MessageType
+mtx::accessors::msg_type(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit(EventMsgType{}, event);
+}
+std::string
+mtx::accessors::room_name(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit(EventRoomName{}, event);
+}
+std::string
+mtx::accessors::room_topic(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit(EventRoomTopic{}, event);
+}
+
+std::string
+mtx::accessors::body(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit(EventBody{}, event);
+}
+
+std::string
+mtx::accessors::formatted_body(const mtx::events::collections::TimelineEvents &event)
+{
+        return std::visit(EventFormattedBody{}, event);
+}
+
+QString
+mtx::accessors::formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event)
+{
+        auto formatted = formatted_body(event);
+        if (!formatted.empty())
+                return QString::fromStdString(formatted);
+        else
+                return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "
"); +} + +std::optional +mtx::accessors::file(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventFile{}, event); +} + +std::string +mtx::accessors::url(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventUrl{}, event); +} +std::string +mtx::accessors::thumbnail_url(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventThumbnailUrl{}, event); +} +std::string +mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventMimeType{}, event); +} +std::string +mtx::accessors::in_reply_to_event(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventInReplyTo{}, event); +} + +std::string +mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventTransactionId{}, event); +} + +int64_t +mtx::accessors::filesize(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventFilesize{}, event); +} + +uint64_t +mtx::accessors::media_height(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventMediaHeight{}, event); +} + +uint64_t +mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(EventMediaWidth{}, event); +} diff --git a/src/EventAccessors.h b/src/EventAccessors.h new file mode 100644 index 00000000..cf79f68f --- /dev/null +++ b/src/EventAccessors.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace mtx::accessors { +std::string +event_id(const mtx::events::collections::TimelineEvents &event); + +std::string +room_id(const mtx::events::collections::TimelineEvents &event); + +std::string +sender(const mtx::events::collections::TimelineEvents &event); + +QDateTime +origin_server_ts(const mtx::events::collections::TimelineEvents &event); + +std::string +filename(const mtx::events::collections::TimelineEvents &event); + +mtx::events::MessageType +msg_type(const mtx::events::collections::TimelineEvents &event); +std::string +room_name(const mtx::events::collections::TimelineEvents &event); +std::string +room_topic(const mtx::events::collections::TimelineEvents &event); + +std::string +body(const mtx::events::collections::TimelineEvents &event); + +std::string +formatted_body(const mtx::events::collections::TimelineEvents &event); + +QString +formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event); + +std::optional +file(const mtx::events::collections::TimelineEvents &event); + +std::string +url(const mtx::events::collections::TimelineEvents &event); +std::string +thumbnail_url(const mtx::events::collections::TimelineEvents &event); +std::string +mimetype(const mtx::events::collections::TimelineEvents &event); +std::string +in_reply_to_event(const mtx::events::collections::TimelineEvents &event); +std::string +transaction_id(const mtx::events::collections::TimelineEvents &event); + +int64_t +filesize(const mtx::events::collections::TimelineEvents &event); + +uint64_t +media_height(const mtx::events::collections::TimelineEvents &event); + +uint64_t +media_width(const mtx::events::collections::TimelineEvents &event); +} diff --git a/src/InviteeItem.h b/src/InviteeItem.h index 85ff7a63..582904b4 100644 --- a/src/InviteeItem.h +++ b/src/InviteeItem.h @@ -3,7 +3,7 @@ #include #include -#include "mtx.hpp" +#include class QPushButton; diff --git a/src/Logging.cpp b/src/Logging.cpp index 686274d8..5d64a630 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -5,17 +5,56 @@ #include "spdlog/sinks/stdout_color_sinks.h" #include +#include +#include + namespace { std::shared_ptr db_logger = nullptr; std::shared_ptr net_logger = nullptr; std::shared_ptr crypto_logger = nullptr; std::shared_ptr ui_logger = nullptr; +std::shared_ptr qml_logger = nullptr; constexpr auto MAX_FILE_SIZE = 1024 * 1024 * 6; constexpr auto MAX_LOG_FILES = 3; + +void +qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + std::string localMsg = msg.toStdString(); + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; + + // Surpress binding wrning for now, as we can't set restore mode to keep compat with qt 5.10 + if (msg.endsWith( + "QML Binding: Not restoring previous value because restoreMode has not been set.This " + "behavior is deprecated.In Qt < 6.0 the default is Binding.RestoreBinding.In Qt >= " + "6.0 the default is Binding.RestoreBindingOrValue.")) + return; + + switch (type) { + case QtDebugMsg: + nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtInfoMsg: + nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtWarningMsg: + nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtCriticalMsg: + nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtFatalMsg: + nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + } +} } namespace nhlog { +bool enable_debug_log_from_commandline = false; + void init(const std::string &file_path) { @@ -33,12 +72,15 @@ init(const std::string &file_path) db_logger = std::make_shared("db", std::begin(sinks), std::end(sinks)); crypto_logger = std::make_shared("crypto", std::begin(sinks), std::end(sinks)); + qml_logger = std::make_shared("qml", std::begin(sinks), std::end(sinks)); if (nheko::enable_debug_log) { db_logger->set_level(spdlog::level::trace); ui_logger->set_level(spdlog::level::trace); crypto_logger->set_level(spdlog::level::trace); } + + qInstallMessageHandler(qmlMessageHandler); } std::shared_ptr @@ -64,4 +106,10 @@ crypto() { return crypto_logger; } + +std::shared_ptr +qml() +{ + return qml_logger; +} } diff --git a/src/Logging.h b/src/Logging.h index 2feae60d..f572afae 100644 --- a/src/Logging.h +++ b/src/Logging.h @@ -18,4 +18,9 @@ db(); std::shared_ptr crypto(); + +std::shared_ptr +qml(); + +extern bool enable_debug_log_from_commandline; } diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index f702832f..20fb3888 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -15,11 +15,14 @@ * along with this program. If not, see . */ +#include #include #include +#include #include "Config.h" +#include "Logging.h" #include "LoginPage.h" #include "MatrixClient.h" #include "ui/FlatButton.h" @@ -108,7 +111,7 @@ LoginPage::LoginPage(QWidget *parent) form_layout_->addLayout(matrixidLayout_); form_layout_->addWidget(password_input_); - form_layout_->addWidget(deviceName_, Qt::AlignHCenter, 0); + form_layout_->addWidget(deviceName_, Qt::AlignHCenter, nullptr); form_layout_->addLayout(serverLayout_); button_layout_ = new QHBoxLayout(); @@ -128,6 +131,7 @@ LoginPage::LoginPage(QWidget *parent) error_label_ = new QLabel(this); error_label_->setFont(font); + error_label_->setWordWrap(true); top_layout_->addLayout(top_bar_layout_); top_layout_->addStretch(1); @@ -186,7 +190,37 @@ LoginPage::onMatrixIdEntered() serverInput_->setText(homeServer); http::client()->set_server(user.hostname()); - checkHomeserverVersion(); + http::client()->well_known([this](const mtx::responses::WellKnown &res, + mtx::http::RequestErr err) { + if (err) { + using namespace boost::beast::http; + + if (err->status_code == status::not_found) { + nhlog::net()->info("Autodiscovery: No .well-known."); + checkHomeserverVersion(); + return; + } + + if (!err->parse_error.empty()) { + emit versionErrorCb( + tr("Autodiscovery failed. Received malformed response.")); + nhlog::net()->error( + "Autodiscovery failed. Received malformed response."); + return; + } + + emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " + "requesting .well-known.")); + nhlog::net()->error("Autodiscovery failed. Unknown error when " + "requesting .well-known."); + return; + } + + nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + + "'"); + http::client()->set_server(res.homeserver.base_url); + checkHomeserverVersion(); + }); } } @@ -272,7 +306,6 @@ LoginPage::onLoginButtonClicked() if (password_input_->text().isEmpty()) return loginError(tr("Empty password")); - http::client()->set_server(serverInput_->text().toStdString()); http::client()->login( user.localpart(), password_input_->text().toStdString(), @@ -285,6 +318,12 @@ LoginPage::onLoginButtonClicked() return; } + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } + emit loginOk(res); }); diff --git a/src/LoginPage.h b/src/LoginPage.h index 99c249b1..4b84abfc 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -38,7 +38,7 @@ class LoginPage : public QWidget Q_OBJECT public: - LoginPage(QWidget *parent = 0); + LoginPage(QWidget *parent = nullptr); void reset(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 7d9a8902..fb64f0fe 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -23,6 +23,7 @@ #include +#include "Cache.h" #include "ChatPage.h" #include "Config.h" #include "Logging.h" @@ -30,6 +31,7 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "RegisterPage.h" +#include "Splitter.h" #include "TrayIcon.h" #include "UserSettingsPage.h" #include "Utils.h" @@ -64,7 +66,7 @@ MainWindow::MainWindow(QWidget *parent) setFont(font); userSettings_ = QSharedPointer(new UserSettings); - trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this); + trayIcon_ = new TrayIcon(":/logos/nheko.svg", this); welcome_page_ = new WelcomePage(this); login_page_ = new LoginPage(this); @@ -113,9 +115,6 @@ MainWindow::MainWindow(QWidget *parent) connect( userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool))); - connect(userSettingsPage_, &UserSettingsPage::themeChanged, this, []() { - Cache::clearUserColors(); - }); connect( userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged); connect(trayIcon_, @@ -190,7 +189,7 @@ MainWindow::resizeEvent(QResizeEvent *event) void MainWindow::adjustSideBars() { - const auto sz = utils::calculateSidebarSizes(QFont{}); + const auto sz = splitter::calculateSidebarSizes(QFont{}); const uint64_t timelineWidth = chat_page_->timelineWidth(); const uint64_t minAvailableWidth = sz.collapsePoint + sz.groups; @@ -444,7 +443,7 @@ MainWindow::openReadReceiptsDialog(const QString &event_id) const auto room_id = chat_page_->currentRoom(); try { - dialog->addUsers(cache::client()->readReceipts(event_id, room_id)); + dialog->addUsers(cache::readReceipts(event_id, room_id)); } catch (const lmdb::error &e) { nhlog::db()->warn("failed to retrieve read receipts for {} {}", event_id.toStdString(), @@ -507,4 +506,34 @@ MainWindow::loadJdenticonPlugin() nhlog::ui()->info("jdenticon plugin not found."); return false; -} \ No newline at end of file +} +void +MainWindow::showWelcomePage() +{ + removeOverlayProgressBar(); + pageStack_->addWidget(welcome_page_); + pageStack_->setCurrentWidget(welcome_page_); +} + +void +MainWindow::showLoginPage() +{ + if (modal_) + modal_->hide(); + + pageStack_->addWidget(login_page_); + pageStack_->setCurrentWidget(login_page_); +} + +void +MainWindow::showRegisterPage() +{ + pageStack_->addWidget(register_page_); + pageStack_->setCurrentWidget(register_page_); +} + +void +MainWindow::showUserSettingsPage() +{ + pageStack_->setCurrentWidget(userSettingsPage_); +} diff --git a/src/MainWindow.h b/src/MainWindow.h index 1aadbf4d..e3e04698 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -24,16 +24,17 @@ #include #include -#include "LoginPage.h" -#include "RegisterPage.h" #include "UserSettingsPage.h" -#include "WelcomePage.h" #include "dialogs/UserProfile.h" #include "ui/OverlayModal.h" #include "jdenticoninterface.h" class ChatPage; +class RegisterPage; +class LoginPage; +class WelcomePage; + class LoadingIndicator; class OverlayModal; class SnackBar; @@ -62,7 +63,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(QWidget *parent = nullptr); static MainWindow *instance() { return instance_; }; void saveCurrentWindowSize(); @@ -97,32 +98,16 @@ private slots: void iconActivated(QSystemTrayIcon::ActivationReason reason); //! Show the welcome page in the main window. - void showWelcomePage() - { - removeOverlayProgressBar(); - pageStack_->addWidget(welcome_page_); - pageStack_->setCurrentWidget(welcome_page_); - } + void showWelcomePage(); //! Show the login page in the main window. - void showLoginPage() - { - if (modal_) - modal_->hide(); - - pageStack_->addWidget(login_page_); - pageStack_->setCurrentWidget(login_page_); - } + void showLoginPage(); //! Show the register page in the main window. - void showRegisterPage() - { - pageStack_->addWidget(register_page_); - pageStack_->setCurrentWidget(register_page_); - } + void showRegisterPage(); //! Show user settings page. - void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); } + void showUserSettingsPage(); //! Show the chat page and start communicating with the given access token. void showChatPage(); diff --git a/src/MatrixClient.cpp b/src/MatrixClient.cpp index 12d7ac91..b69ba480 100644 --- a/src/MatrixClient.cpp +++ b/src/MatrixClient.cpp @@ -2,6 +2,26 @@ #include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include + +Q_DECLARE_METATYPE(mtx::responses::Login) +Q_DECLARE_METATYPE(mtx::responses::Messages) +Q_DECLARE_METATYPE(mtx::responses::Notifications) +Q_DECLARE_METATYPE(mtx::responses::Rooms) +Q_DECLARE_METATYPE(mtx::responses::Sync) +Q_DECLARE_METATYPE(mtx::responses::JoinedGroups) +Q_DECLARE_METATYPE(mtx::responses::GroupProfile) + +Q_DECLARE_METATYPE(nlohmann::json) +Q_DECLARE_METATYPE(std::string) +Q_DECLARE_METATYPE(std::vector) +Q_DECLARE_METATYPE(std::vector) + namespace { auto client_ = std::make_shared(); } diff --git a/src/MatrixClient.h b/src/MatrixClient.h index 2af57267..4db51095 100644 --- a/src/MatrixClient.h +++ b/src/MatrixClient.h @@ -1,35 +1,7 @@ #pragma once -#include -#include -#include - -#include "nlohmann/json.hpp" -#include #include -Q_DECLARE_METATYPE(mtx::responses::Login) -Q_DECLARE_METATYPE(mtx::responses::Messages) -Q_DECLARE_METATYPE(mtx::responses::Notifications) -Q_DECLARE_METATYPE(mtx::responses::Rooms) -Q_DECLARE_METATYPE(mtx::responses::Sync) -Q_DECLARE_METATYPE(mtx::responses::JoinedGroups) -Q_DECLARE_METATYPE(mtx::responses::GroupProfile) -Q_DECLARE_METATYPE(std::string) -Q_DECLARE_METATYPE(nlohmann::json) -Q_DECLARE_METATYPE(std::vector) -Q_DECLARE_METATYPE(std::vector) - -class MediaProxy : public QObject -{ - Q_OBJECT - -signals: - void imageDownloaded(const QPixmap &); - void imageSaved(const QString &, const QByteArray &); - void fileDownloaded(const QByteArray &); -}; - namespace http { mtx::http::Client * client(); diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp new file mode 100644 index 00000000..d04eab24 --- /dev/null +++ b/src/MxcImageProvider.cpp @@ -0,0 +1,85 @@ +#include "MxcImageProvider.h" + +#include "Cache.h" +#include "Logging.h" +#include "MatrixClient.h" + +void +MxcImageResponse::run() +{ + if (m_requestedSize.isValid() && !m_encryptionInfo) { + QString fileName = QString("%1_%2x%3_crop") + .arg(m_id) + .arg(m_requestedSize.width()) + .arg(m_requestedSize.height()); + + auto data = cache::image(fileName); + if (!data.isNull() && m_image.loadFromData(data)) { + m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio); + m_image.setText("mxc url", "mxc://" + m_id); + emit finished(); + return; + } + + mtx::http::ThumbOpts opts; + opts.mxc_url = "mxc://" + m_id.toStdString(); + opts.width = m_requestedSize.width() > 0 ? m_requestedSize.width() : -1; + opts.height = m_requestedSize.height() > 0 ? m_requestedSize.height() : -1; + opts.method = "crop"; + http::client()->get_thumbnail( + opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to download image {}", + m_id.toStdString()); + m_error = "Failed download"; + emit finished(); + + return; + } + + auto data = QByteArray(res.data(), res.size()); + cache::saveImage(fileName, data); + m_image.loadFromData(data); + m_image.setText("mxc url", "mxc://" + m_id); + + emit finished(); + }); + } else { + auto data = cache::image(m_id); + if (!data.isNull() && m_image.loadFromData(data)) { + m_image.setText("mxc url", "mxc://" + m_id); + emit finished(); + return; + } + + http::client()->download( + "mxc://" + m_id.toStdString(), + [this](const std::string &res, + const std::string &, + const std::string &originalFilename, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to download image {}", + m_id.toStdString()); + m_error = "Failed download"; + emit finished(); + + return; + } + + auto temp = res; + if (m_encryptionInfo) + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, m_encryptionInfo.value())); + + auto data = QByteArray(temp.data(), temp.size()); + m_image.loadFromData(data); + m_image.setText("original filename", + QString::fromStdString(originalFilename)); + m_image.setText("mxc url", "mxc://" + m_id); + cache::saveImage(m_id, data); + + emit finished(); + }); + } +} diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h new file mode 100644 index 00000000..2c197a13 --- /dev/null +++ b/src/MxcImageProvider.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#include + +class MxcImageResponse + : public QQuickImageResponse + , public QRunnable +{ +public: + MxcImageResponse(const QString &id, + const QSize &requestedSize, + boost::optional encryptionInfo) + : m_id(id) + , m_requestedSize(requestedSize) + , m_encryptionInfo(encryptionInfo) + { + setAutoDelete(false); + } + + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_image); + } + QString errorString() const override { return m_error; } + + void run() override; + + QString m_id, m_error; + QSize m_requestedSize; + QImage m_image; + boost::optional m_encryptionInfo; +}; + +class MxcImageProvider + : public QObject + , public QQuickAsyncImageProvider +{ + Q_OBJECT +public slots: + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override + { + boost::optional info; + auto temp = infos.find("mxc://" + id); + if (temp != infos.end()) + info = *temp; + + MxcImageResponse *response = new MxcImageResponse(id, requestedSize, info); + pool.start(response); + return response; + } + + void addEncryptionInfo(mtx::crypto::EncryptedFile info) + { + infos.insert(QString::fromStdString(info.url), info); + } + +private: + QThreadPool pool; + QHash infos; +}; diff --git a/src/Olm.cpp b/src/Olm.cpp index c1598570..78b16be7 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1,4 +1,4 @@ -#include +#include #include "Olm.h" @@ -121,7 +121,7 @@ handle_pre_key_olm_message(const std::string &sender, // We also remove the one time key used to establish that // session so we'll have to update our copy of the account object. - cache::client()->saveOlmAccount(olm::client()->save("secret")); + cache::saveOlmAccount(olm::client()->save("secret")); } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical( "failed to create inbound session with {}: {}", sender, e.what()); @@ -149,7 +149,7 @@ handle_pre_key_olm_message(const std::string &sender, nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2)); try { - cache::client()->saveOlmSession(sender_key, std::move(inbound_session)); + cache::saveOlmSession(sender_key, std::move(inbound_session)); } catch (const lmdb::error &e) { nhlog::db()->warn( "failed to save inbound olm session from {}: {}", sender, e.what()); @@ -159,15 +159,20 @@ handle_pre_key_olm_message(const std::string &sender, } mtx::events::msg::Encrypted -encrypt_group_message(const std::string &room_id, - const std::string &device_id, - const std::string &body) +encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body) { using namespace mtx::events; - // Always chech before for existence. - auto res = cache::client()->getOutboundMegolmSession(room_id); - auto payload = olm::client()->encrypt_group_message(res.session, body); + // relations shouldn't be encrypted... + mtx::common::RelatesTo relation; + if (body["content"].count("m.relates_to") != 0) { + relation = body["content"]["m.relates_to"]; + body["content"].erase("m.relates_to"); + } + + // Always check before for existence. + auto res = cache::getOutboundMegolmSession(room_id); + auto payload = olm::client()->encrypt_group_message(res.session, body.dump()); // Prepare the m.room.encrypted event. msg::Encrypted data; @@ -176,12 +181,13 @@ encrypt_group_message(const std::string &room_id, data.session_id = res.data.session_id; data.device_id = device_id; data.algorithm = MEGOLM_ALGO; + data.relates_to = relation; auto message_index = olm_outbound_group_session_message_index(res.session); nhlog::crypto()->info("next message_index {}", message_index); // We need to re-pickle the session after we send a message to save the new message_index. - cache::client()->updateOutboundMegolmSession(room_id, message_index); + cache::updateOutboundMegolmSession(room_id, message_index); return data; } @@ -189,13 +195,13 @@ encrypt_group_message(const std::string &room_id, nlohmann::json try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCipherContent &msg) { - auto session_ids = cache::client()->getOlmSessions(sender_key); + auto session_ids = cache::getOlmSessions(sender_key); nhlog::crypto()->info("attempt to decrypt message with {} known session_ids", session_ids.size()); for (const auto &id : session_ids) { - auto session = cache::client()->getOlmSession(sender_key, id); + auto session = cache::getOlmSession(sender_key, id); if (!session) continue; @@ -204,7 +210,7 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip try { text = olm::client()->decrypt_message(session->get(), msg.type, msg.body); - cache::client()->saveOlmSession(id, std::move(session.value())); + cache::saveOlmSession(id, std::move(session.value())); } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}", msg.type, @@ -252,7 +258,7 @@ create_inbound_megolm_session(const std::string &sender, try { auto megolm_session = olm::client()->init_inbound_group_session(session_key); - cache::client()->saveInboundMegolmSession(index, std::move(megolm_session)); + cache::saveInboundMegolmSession(index, std::move(megolm_session)); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); return; @@ -268,7 +274,7 @@ void mark_keys_as_published() { olm::client()->mark_keys_as_published(); - cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY)); + cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY)); } void @@ -289,14 +295,13 @@ request_keys(const std::string &room_id, const std::string &event_id) return; } - if (boost::get>(&res) == nullptr) { + if (!std::holds_alternative>(res)) { nhlog::net()->info( "retrieved event is not encrypted: {} from {}", event_id, room_id); return; } - olm::send_key_request_for(room_id, - boost::get>(res)); + olm::send_key_request_for(room_id, std::get>(res)); }); } @@ -356,13 +361,13 @@ handle_key_request_message(const mtx::events::msg::KeyRequest &req) } // Check if we have the keys for the requested session. - if (!cache::client()->outboundMegolmSessionExists(req.room_id)) { + if (!cache::outboundMegolmSessionExists(req.room_id)) { nhlog::crypto()->warn("requested session not found in room: {}", req.room_id); return; } // Check that the requested session_id and the one we have saved match. - const auto session = cache::client()->getOutboundMegolmSession(req.room_id); + const auto session = cache::getOutboundMegolmSession(req.room_id); if (req.session_id != session.data.session_id) { nhlog::crypto()->warn("session id of retrieved session doesn't match the request: " "requested({}), ours({})", @@ -371,7 +376,7 @@ handle_key_request_message(const mtx::events::msg::KeyRequest &req) return; } - if (!cache::client()->isRoomMember(req.sender, req.room_id)) { + if (!cache::isRoomMember(req.sender, req.room_id)) { nhlog::crypto()->warn( "user {} that requested the session key is not member of the room {}", req.sender, @@ -510,8 +515,7 @@ send_megolm_key_to_device(const std::string &user_id, device_msg = olm::client()->create_olm_encrypted_content( olm_session.get(), room_key, pks.curve25519); - cache::client()->saveOlmSession(pks.curve25519, - std::move(olm_session)); + cache::saveOlmSession(pks.curve25519, std::move(olm_session)); } catch (const json::exception &e) { nhlog::crypto()->warn("creating outbound session: {}", e.what()); diff --git a/src/Olm.h b/src/Olm.h index ae4e0659..28521413 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -3,7 +3,8 @@ #include #include -#include +#include +#include #include constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2"; @@ -62,7 +63,7 @@ handle_pre_key_olm_message(const std::string &sender, mtx::events::msg::Encrypted encrypt_group_message(const std::string &room_id, const std::string &device_id, - const std::string &body); + nlohmann::json body); void mark_keys_as_published(); diff --git a/src/QuickSwitcher.cpp b/src/QuickSwitcher.cpp index eb79a427..05a9f431 100644 --- a/src/QuickSwitcher.cpp +++ b/src/QuickSwitcher.cpp @@ -22,8 +22,11 @@ #include #include +#include "Cache.h" #include "QuickSwitcher.h" -#include "SuggestionsPopup.h" +#include "popups/SuggestionsPopup.h" + +Q_DECLARE_METATYPE(std::vector) RoomSearchInput::RoomSearchInput(QWidget *parent) : TextField(parent) @@ -93,8 +96,7 @@ QuickSwitcher::QuickSwitcher(QWidget *parent) QtConcurrent::run([this, query = query.toLower()]() { try { - emit queryResults( - cache::client()->searchRooms(query.toStdString())); + emit queryResults(cache::searchRooms(query.toStdString())); } catch (const lmdb::error &e) { qWarning() << "room search failed:" << e.what(); } diff --git a/src/QuickSwitcher.h b/src/QuickSwitcher.h index 24b9adfa..5bc31650 100644 --- a/src/QuickSwitcher.h +++ b/src/QuickSwitcher.h @@ -22,11 +22,9 @@ #include #include -#include "SuggestionsPopup.h" +#include "popups/SuggestionsPopup.h" #include "ui/TextField.h" -Q_DECLARE_METATYPE(std::vector) - class RoomSearchInput : public TextField { Q_OBJECT diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index fdb0f43a..39a69a34 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -15,9 +15,13 @@ * along with this program. If not, see . */ +#include +#include #include #include +#include + #include "Config.h" #include "Logging.h" #include "MainWindow.h" @@ -27,11 +31,17 @@ #include "ui/RaisedButton.h" #include "ui/TextField.h" +#include "dialogs/FallbackAuth.h" #include "dialogs/ReCaptcha.h" +Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized) +Q_DECLARE_METATYPE(mtx::user_interactive::Auth) + RegisterPage::RegisterPage(QWidget *parent) : QWidget(parent) { + qRegisterMetaType(); + qRegisterMetaType(); top_layout_ = new QVBoxLayout(); back_layout_ = new QHBoxLayout(); @@ -87,10 +97,10 @@ RegisterPage::RegisterPage(QWidget *parent) server_input_ = new TextField(); server_input_->setLabel(tr("Home Server")); - form_layout_->addWidget(username_input_, Qt::AlignHCenter, 0); - form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0); - form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter, 0); - form_layout_->addWidget(server_input_, Qt::AlignHCenter, 0); + form_layout_->addWidget(username_input_, Qt::AlignHCenter, nullptr); + form_layout_->addWidget(password_input_, Qt::AlignHCenter, nullptr); + form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter, nullptr); + form_layout_->addWidget(server_input_, Qt::AlignHCenter, nullptr); button_layout_ = new QHBoxLayout(); button_layout_->setSpacing(0); @@ -130,46 +140,139 @@ RegisterPage::RegisterPage(QWidget *parent) this, &RegisterPage::registrationFlow, this, - [this](const std::string &user, const std::string &pass, const std::string &session) { - emit errorOccurred(); + [this](const std::string &user, + const std::string &pass, + const mtx::user_interactive::Unauthorized &unauthorized) { + auto completed_stages = unauthorized.completed; + auto flows = unauthorized.flows; + auto session = unauthorized.session; - auto captchaDialog = - new dialogs::ReCaptcha(QString::fromStdString(session), this); + nhlog::ui()->info("Completed stages: {}", completed_stages.size()); - connect(captchaDialog, - &dialogs::ReCaptcha::confirmation, - this, - [this, user, pass, session, captchaDialog]() { - captchaDialog->close(); - captchaDialog->deleteLater(); + if (!completed_stages.empty()) + flows.erase(std::remove_if( + flows.begin(), + flows.end(), + [completed_stages](auto flow) { + if (completed_stages.size() > flow.stages.size()) + return true; + for (size_t f = 0; f < completed_stages.size(); f++) + if (completed_stages[f] != flow.stages[f]) + return true; + return false; + }), + flows.end()); - emit registering(); + if (flows.empty()) { + nhlog::net()->error("No available registration flows!"); + emit registerErrorCb(tr("No supported registration flows!")); + return; + } - http::client()->flow_response( - user, - pass, - session, - "m.login.recaptcha", - [this](const mtx::responses::Register &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to retrieve registration flows: {}", - err->matrix_error.error); - emit errorOccurred(); - emit registerErrorCb(QString::fromStdString( - err->matrix_error.error)); - return; - } + auto current_stage = flows.front().stages.at(completed_stages.size()); - http::client()->set_user(res.user_id); - http::client()->set_access_token(res.access_token); + if (current_stage == mtx::user_interactive::auth_types::recaptcha) { + auto captchaDialog = + new dialogs::ReCaptcha(QString::fromStdString(session), this); - emit registerOk(); - }); - }); + connect(captchaDialog, + &dialogs::ReCaptcha::confirmation, + this, + [this, user, pass, session, captchaDialog]() { + captchaDialog->close(); + captchaDialog->deleteLater(); - QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); + emit registerAuth( + user, + pass, + mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::Fallback{}}); + }); + connect(captchaDialog, + &dialogs::ReCaptcha::cancel, + this, + &RegisterPage::errorOccurred); + + QTimer::singleShot( + 1000, this, [captchaDialog]() { captchaDialog->show(); }); + } else if (current_stage == mtx::user_interactive::auth_types::dummy) { + emit registerAuth(user, + pass, + mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::Dummy{}}); + } else { + // use fallback + auto dialog = + new dialogs::FallbackAuth(QString::fromStdString(current_stage), + QString::fromStdString(session), + this); + + connect(dialog, + &dialogs::FallbackAuth::confirmation, + this, + [this, user, pass, session, dialog]() { + dialog->close(); + dialog->deleteLater(); + + emit registerAuth( + user, + pass, + mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::Fallback{}}); + }); + connect(dialog, + &dialogs::FallbackAuth::cancel, + this, + &RegisterPage::errorOccurred); + + dialog->show(); + } + }); + + connect( + this, + &RegisterPage::registerAuth, + this, + [this](const std::string &user, + const std::string &pass, + const mtx::user_interactive::Auth &auth) { + http::client()->registration( + user, + pass, + auth, + [this, user, pass](const mtx::responses::Register &res, + mtx::http::RequestErr err) { + if (!err) { + http::client()->set_user(res.user_id); + http::client()->set_access_token(res.access_token); + + emit registerOk(); + return; + } + + // The server requires registration flows. + if (err->status_code == boost::beast::http::status::unauthorized) { + if (err->matrix_error.unauthorized.session.empty()) { + nhlog::net()->warn( + "failed to retrieve registration flows: ({}) " + "{}", + static_cast(err->status_code), + err->matrix_error.error); + emit registerErrorCb( + QString::fromStdString(err->matrix_error.error)); + return; + } + + emit registrationFlow( + user, pass, err->matrix_error.unauthorized); + return; + } + + nhlog::net()->warn("failed to register: status_code ({})", + static_cast(err->status_code)); + + emit registerErrorCb(QString::fromStdString(err->matrix_error.error)); + }); }); setLayout(top_layout_); @@ -222,31 +325,27 @@ RegisterPage::onRegisterButtonClicked() // The server requires registration flows. if (err->status_code == boost::beast::http::status::unauthorized) { - http::client()->flow_register( - username, - password, - [this, username, password]( - const mtx::responses::RegistrationFlows &res, - mtx::http::RequestErr err) { - if (res.session.empty() && err) { - nhlog::net()->warn( - "failed to retrieve registration flows: ({}) " - "{}", - static_cast(err->status_code), - err->matrix_error.error); - emit errorOccurred(); - emit registerErrorCb(QString::fromStdString( - err->matrix_error.error)); - return; - } + if (err->matrix_error.unauthorized.session.empty()) { + nhlog::net()->warn( + "failed to retrieve registration flows: ({}) " + "{}", + static_cast(err->status_code), + err->matrix_error.error); + emit errorOccurred(); + emit registerErrorCb( + QString::fromStdString(err->matrix_error.error)); + return; + } - emit registrationFlow(username, password, res.session); - }); + emit registrationFlow( + username, password, err->matrix_error.unauthorized); return; } - nhlog::net()->warn("failed to register: status_code ({})", - static_cast(err->status_code)); + nhlog::net()->warn( + "failed to register: status_code ({}), matrix_error({})", + static_cast(err->status_code), + err->matrix_error.error); emit registerErrorCb(QString::fromStdString(err->matrix_error.error)); emit errorOccurred(); diff --git a/src/RegisterPage.h b/src/RegisterPage.h index b05cf150..ebc24bb1 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -21,6 +21,8 @@ #include #include +#include + class FlatButton; class RaisedButton; class TextField; @@ -30,7 +32,7 @@ class RegisterPage : public QWidget Q_OBJECT public: - RegisterPage(QWidget *parent = 0); + RegisterPage(QWidget *parent = nullptr); protected: void paintEvent(QPaintEvent *event) override; @@ -43,7 +45,10 @@ signals: void registerErrorCb(const QString &msg); void registrationFlow(const std::string &user, const std::string &pass, - const std::string &session); + const mtx::user_interactive::Unauthorized &unauthorized); + void registerAuth(const std::string &user, + const std::string &pass, + const mtx::user_interactive::Auth &auth); private slots: void onBackButtonClicked(); diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp index f17b383c..4c8535bf 100644 --- a/src/RoomInfoListItem.cpp +++ b/src/RoomInfoListItem.cpp @@ -16,13 +16,16 @@ */ #include -#include #include #include +#include +#include +#include "AvatarProvider.h" #include "Cache.h" #include "Config.h" #include "RoomInfoListItem.h" +#include "Splitter.h" #include "Utils.h" #include "ui/Menu.h" #include "ui/Ripple.h" @@ -30,9 +33,6 @@ constexpr int MaxUnreadCountDisplayed = 99; -constexpr int IconSize = 44; -// constexpr int MaxHeight = IconSize + 2 * Padding; - struct WidgetMetrics { int maxHeight; @@ -62,7 +62,7 @@ getMetrics(const QFont &font) m.unreadLineOffset = m.padding - m.padding / 4; m.inviteBtnX = m.iconSize + 2 * m.padding; - m.inviteBtnX = m.iconSize / 2.0 + m.padding + m.padding / 3.0; + m.inviteBtnY = m.iconSize / 2.0 + m.padding + m.padding / 3.0; return m; } @@ -74,7 +74,8 @@ RoomInfoListItem::init(QWidget *parent) setMouseTracking(true); setAttribute(Qt::WA_Hover); - setFixedHeight(getMetrics(QFont{}).maxHeight); + auto wm = getMetrics(QFont{}); + setFixedHeight(wm.maxHeight); QPainterPath path; path.addRect(0, 0, parent->width(), height()); @@ -83,6 +84,10 @@ RoomInfoListItem::init(QWidget *parent) ripple_overlay_->setClipPath(path); ripple_overlay_->setClipping(true); + avatar_ = new Avatar(this, wm.iconSize); + avatar_->setLetter(utils::firstChar(roomName_)); + avatar_->move(wm.padding, wm.padding); + unreadCountFont_.setPointSizeF(unreadCountFont_.pointSizeF() * 0.8); unreadCountFont_.setBold(true); @@ -94,7 +99,7 @@ RoomInfoListItem::init(QWidget *parent) menu_->addAction(leaveRoom_); } -RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent) +RoomInfoListItem::RoomInfoListItem(QString room_id, const RoomInfo &info, QWidget *parent) : QWidget(parent) , roomType_{info.is_invite ? RoomType::Invited : RoomType::Joined} , roomId_(std::move(room_id)) @@ -104,18 +109,6 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare , unreadHighlightedMsgCount_(0) { init(parent); - - QString emptyEventId; - - // HACK - // We use fake message info with an old date to pin - // the invite events to the top. - // - // State events in invited rooms don't contain timestamp info, - // so we can't use them for sorting. - if (roomType_ == RoomType::Invited) - lastMsgInfo_ = { - emptyEventId, "-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)}; } void @@ -125,7 +118,7 @@ RoomInfoListItem::resizeEvent(QResizeEvent *) QPainterPath path; path.addRect(0, 0, width(), height()); - const auto sidebarSizes = utils::calculateSidebarSizes(QFont{}); + const auto sidebarSizes = splitter::calculateSidebarSizes(QFont{}); if (width() > sidebarSizes.small) setToolTip(""); @@ -167,12 +160,10 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) subtitlePen.setColor(subtitleColor_); } - QRect avatarRegion(wm.padding, wm.padding, wm.iconSize, wm.iconSize); - // Description line with the default font. int bottom_y = wm.maxHeight - wm.padding - metrics.ascent() / 2; - const auto sidebarSizes = utils::calculateSidebarSizes(QFont{}); + const auto sidebarSizes = splitter::calculateSidebarSizes(QFont{}); if (width() > sidebarSizes.small) { QFont headingFont; @@ -182,8 +173,12 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) QFont tsFont; tsFont.setPointSizeF(tsFont.pointSizeF() * 0.9); +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) const int msgStampWidth = QFontMetrics(tsFont).width(lastMsgInfo_.timestamp) + 4; - +#else + const int msgStampWidth = + QFontMetrics(tsFont).horizontalAdvance(lastMsgInfo_.timestamp) + 4; +#endif // We use the full width of the widget if there is no unread msg bubble. const int bottomLineWidthLimit = (unreadMsgCount_ > 0) ? msgStampWidth : 0; @@ -201,30 +196,11 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) p.setFont(QFont{}); p.setPen(subtitlePen); - // The limit is the space between the end of the avatar and the start of the - // timestamp. - int usernameLimit = - std::max(0, width() - 3 * wm.padding - msgStampWidth - wm.iconSize - 20); - auto userName = - metrics.elidedText(lastMsgInfo_.username, Qt::ElideRight, usernameLimit); - - p.setFont(QFont{}); - p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), userName); - - int nameWidth = QFontMetrics(QFont{}).width(userName); - - p.setFont(QFont{}); - - // The limit is the space between the end of the username and the start of - // the timestamp. - int descriptionLimit = - std::max(0, - width() - 3 * wm.padding - bottomLineWidthLimit - wm.iconSize - - nameWidth - 5); + int descriptionLimit = std::max( + 0, width() - 3 * wm.padding - bottomLineWidthLimit - wm.iconSize); auto description = metrics.elidedText(lastMsgInfo_.body, Qt::ElideRight, descriptionLimit); - p.drawText(QPoint(2 * wm.padding + wm.iconSize + nameWidth, bottom_y), - description); + p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), description); // We show the last message timestamp. p.save(); @@ -263,42 +239,17 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) p.setPen(QPen(btnTextColor_)); p.setFont(QFont{}); - p.drawText(acceptBtnRegion_, Qt::AlignCenter, tr("Accept")); - p.drawText(declineBtnRegion_, Qt::AlignCenter, tr("Decline")); + p.drawText(acceptBtnRegion_, + Qt::AlignCenter, + metrics.elidedText(tr("Accept"), Qt::ElideRight, btnWidth)); + p.drawText(declineBtnRegion_, + Qt::AlignCenter, + metrics.elidedText(tr("Decline"), Qt::ElideRight, btnWidth)); } } p.setPen(Qt::NoPen); - // We using the first letter of room's name. - if (roomAvatar_.isNull()) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(avatarBgColor()); - - p.setPen(Qt::NoPen); - p.setBrush(brush); - - p.drawEllipse(avatarRegion.center(), wm.iconSize / 2, wm.iconSize / 2); - - QFont bubbleFont; - bubbleFont.setPointSizeF(bubbleFont.pointSizeF() * 1.4); - p.setFont(bubbleFont); - p.setPen(avatarFgColor()); - p.setBrush(Qt::NoBrush); - p.drawText( - avatarRegion.translated(0, -1), Qt::AlignCenter, utils::firstChar(roomName())); - } else { - p.save(); - - QPainterPath path; - path.addEllipse(wm.padding, wm.padding, wm.iconSize, wm.iconSize); - p.setClipPath(path); - - p.drawPixmap(avatarRegion, roomAvatar_); - p.restore(); - } - if (unreadMsgCount_ > 0) { QBrush brush; brush.setStyle(Qt::SolidPattern); @@ -426,10 +377,9 @@ RoomInfoListItem::mousePressEvent(QMouseEvent *event) } void -RoomInfoListItem::setAvatar(const QImage &img) +RoomInfoListItem::setAvatar(const QString &avatar_url) { - roomAvatar_ = utils::scaleImageToPixmap(img, IconSize); - update(); + avatar_->setImage(avatar_url); } void diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h index 40c938c1..c1ee533d 100644 --- a/src/RoomInfoListItem.h +++ b/src/RoomInfoListItem.h @@ -22,9 +22,11 @@ #include #include -#include "Cache.h" #include +#include "CacheStructs.h" +#include "ui/Avatar.h" + class Menu; class RippleOverlay; @@ -37,9 +39,6 @@ class RoomInfoListItem : public QWidget QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor) Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) - Q_PROPERTY(QColor avatarBgColor READ avatarBgColor WRITE setAvatarBgColor) - Q_PROPERTY(QColor avatarFgColor READ avatarFgColor WRITE setAvatarFgColor) - Q_PROPERTY(QColor bubbleBgColor READ bubbleBgColor WRITE setBubbleBgColor) Q_PROPERTY(QColor bubbleFgColor READ bubbleFgColor WRITE setBubbleFgColor) @@ -64,7 +63,7 @@ class RoomInfoListItem : public QWidget Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor) public: - RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent = 0); + RoomInfoListItem(QString room_id, const RoomInfo &info, QWidget *parent = nullptr); void updateUnreadMessageCount(int count, int highlightedCount); void clearUnreadMessageCount() { updateUnreadMessageCount(0, 0); }; @@ -73,7 +72,7 @@ public: bool isPressed() const { return isPressed_; } int unreadMessageCount() const { return unreadMsgCount_; } - void setAvatar(const QImage &avatar_image); + void setAvatar(const QString &avatar_url); void setDescriptionMessage(const DescInfo &info); DescInfo lastMessageInfo() const { return lastMsgInfo_; } @@ -83,8 +82,6 @@ public: QColor hoverSubtitleColor() const { return hoverSubtitleColor_; } QColor hoverTimestampColor() const { return hoverTimestampColor_; } QColor backgroundColor() const { return backgroundColor_; } - QColor avatarBgColor() const { return avatarBgColor_; } - QColor avatarFgColor() const { return avatarFgColor_; } QColor highlightedTitleColor() const { return highlightedTitleColor_; } QColor highlightedSubtitleColor() const { return highlightedSubtitleColor_; } @@ -107,8 +104,6 @@ public: void setHoverTimestampColor(QColor &color) { hoverTimestampColor_ = color; } void setBackgroundColor(QColor &color) { backgroundColor_ = color; } void setTimestampColor(QColor &color) { timestampColor_ = color; } - void setAvatarFgColor(QColor &color) { avatarFgColor_ = color; } - void setAvatarBgColor(QColor &color) { avatarBgColor_ = color; } void setHighlightedTitleColor(QColor &color) { highlightedTitleColor_ = color; } void setHighlightedSubtitleColor(QColor &color) { highlightedSubtitleColor_ = color; } @@ -162,6 +157,7 @@ private: QString roomName() { return roomName_; } RippleOverlay *ripple_overlay_; + Avatar *avatar_; enum class RoomType { @@ -179,8 +175,6 @@ private: DescInfo lastMsgInfo_; - QPixmap roomAvatar_; - Menu *menu_; QAction *leaveRoom_; @@ -218,9 +212,6 @@ private: QColor highlightedTimestampColor_; QColor hoverTimestampColor_; - QColor avatarBgColor_; - QColor avatarFgColor_; - QColor bubbleBgColor_; QColor bubbleFgColor_; }; diff --git a/src/RoomList.cpp b/src/RoomList.cpp index 1abf3533..6feb4f76 100644 --- a/src/RoomList.cpp +++ b/src/RoomList.cpp @@ -15,18 +15,17 @@ * along with this program. If not, see . */ -#include -#include +#include + #include +#include +#include #include -#include "Cache.h" #include "Logging.h" #include "MainWindow.h" -#include "MatrixClient.h" #include "RoomInfoListItem.h" #include "RoomList.h" -#include "UserSettingsPage.h" #include "Utils.h" #include "ui/OverlayModal.h" @@ -43,6 +42,8 @@ RoomList::RoomList(QWidget *parent) scrollArea_->setWidgetResizable(true); scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter); + QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); + // The scrollbar on macOS will hide itself when not active so it won't interfere // with the content. #if not defined(Q_OS_MAC) @@ -89,40 +90,7 @@ RoomList::updateAvatar(const QString &room_id, const QString &url) if (url.isEmpty()) return; - QByteArray savedImgData; - - if (cache::client()) - savedImgData = cache::client()->image(url); - - if (savedImgData.isEmpty()) { - mtx::http::ThumbOpts opts; - opts.mxc_url = url.toStdString(); - http::client()->get_thumbnail( - opts, [room_id, opts, this](const std::string &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to download room avatar: {} {} {}", - opts.mxc_url, - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - return; - } - - if (cache::client()) - cache::client()->saveImage(opts.mxc_url, res); - - auto data = QByteArray(res.data(), res.size()); - QPixmap pixmap; - pixmap.loadFromData(data); - - emit updateRoomAvatarCb(room_id, pixmap); - }); - } else { - QPixmap img; - img.loadFromData(savedImgData); - - updateRoomAvatar(room_id, img); - } + emit updateRoomAvatarCb(room_id, url); } void @@ -193,6 +161,8 @@ RoomList::initialize(const QMap &info) if (rooms_.empty()) return; + sortRoomsByLastMessage(); + auto room = firstRoom(); if (room.second.isNull()) return; @@ -224,6 +194,9 @@ RoomList::sync(const std::map &info) { for (const auto &room : info) updateRoom(room.first, room.second); + + if (!info.empty()) + sortRoomsByLastMessage(); } void @@ -252,7 +225,73 @@ RoomList::highlightSelectedRoom(const QString &room_id) } void -RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img) +RoomList::nextRoom() +{ + for (int ii = 0; ii < contentsLayout_->count() - 1; ++ii) { + auto room = qobject_cast(contentsLayout_->itemAt(ii)->widget()); + + if (!room) + continue; + + if (room->roomId() == selectedRoom_) { + auto nextRoom = qobject_cast( + contentsLayout_->itemAt(ii + 1)->widget()); + + // Not a room message. + if (!nextRoom || nextRoom->isInvite()) + return; + + emit roomChanged(nextRoom->roomId()); + if (!roomExists(nextRoom->roomId())) { + nhlog::ui()->warn("roomlist: clicked unknown room_id"); + return; + } + + room->setPressedState(false); + nextRoom->setPressedState(true); + + scrollArea_->ensureWidgetVisible(nextRoom); + selectedRoom_ = nextRoom->roomId(); + return; + } + } +} + +void +RoomList::previousRoom() +{ + for (int ii = 1; ii < contentsLayout_->count(); ++ii) { + auto room = qobject_cast(contentsLayout_->itemAt(ii)->widget()); + + if (!room) + continue; + + if (room->roomId() == selectedRoom_) { + auto nextRoom = qobject_cast( + contentsLayout_->itemAt(ii - 1)->widget()); + + // Not a room message. + if (!nextRoom || nextRoom->isInvite()) + return; + + emit roomChanged(nextRoom->roomId()); + if (!roomExists(nextRoom->roomId())) { + nhlog::ui()->warn("roomlist: clicked unknown room_id"); + return; + } + + room->setPressedState(false); + nextRoom->setPressedState(true); + + scrollArea_->ensureWidgetVisible(nextRoom); + selectedRoom_ = nextRoom->roomId(); + return; + } + } +} + +void +RoomList::updateRoomAvatar(const QString &roomid, const QString &img) { if (!roomExists(roomid)) { nhlog::ui()->warn("avatar update on non-existent room_id: {}", @@ -260,7 +299,7 @@ RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img) return; } - rooms_[roomid]->setAvatar(img.toImage()); + rooms_[roomid]->setAvatar(img); // Used to inform other widgets for the new image data. emit roomAvatarChanged(roomid, img); @@ -303,7 +342,9 @@ RoomList::sortRoomsByLastMessage() continue; // Not a room message. - if (room->lastMessageInfo().userid.isEmpty()) + if (room->isInvite()) + times.emplace(std::numeric_limits::max(), room); + else if (room->lastMessageInfo().userid.isEmpty()) times.emplace(0, room); else times.emplace(room->lastMessageInfo().datetime.toMSecsSinceEpoch(), room); @@ -443,13 +484,16 @@ RoomList::addInvitedRoom(const QString &room_id, const RoomInfo &info) std::pair> RoomList::firstRoom() const { - auto firstRoom = rooms_.begin(); + for (int i = 0; i < contentsLayout_->count(); i++) { + auto item = qobject_cast(contentsLayout_->itemAt(i)->widget()); - while (firstRoom->second.isNull() && firstRoom != rooms_.end()) - firstRoom++; + if (item) { + return std::pair>( + item->roomId(), rooms_.at(item->roomId())); + } + } - return std::pair>(firstRoom->first, - firstRoom->second); + return {}; } void diff --git a/src/RoomList.h b/src/RoomList.h index 155a969c..fef552c6 100644 --- a/src/RoomList.h +++ b/src/RoomList.h @@ -17,15 +17,12 @@ #pragma once -#include #include #include #include #include #include -#include - class LeaveRoomDialog; class OverlayModal; class RoomInfoListItem; @@ -38,7 +35,7 @@ class RoomList : public QWidget Q_OBJECT public: - explicit RoomList(QWidget *parent = 0); + explicit RoomList(QWidget *parent = nullptr); void initialize(const QMap &info); void sync(const std::map &info); @@ -61,17 +58,19 @@ signals: void totalUnreadMessageCountUpdated(int count); void acceptInvite(const QString &room_id); void declineInvite(const QString &room_id); - void roomAvatarChanged(const QString &room_id, const QPixmap &img); + void roomAvatarChanged(const QString &room_id, const QString &img); void joinRoom(const QString &room_id); - void updateRoomAvatarCb(const QString &room_id, const QPixmap &img); + void updateRoomAvatarCb(const QString &room_id, const QString &img); public slots: - void updateRoomAvatar(const QString &roomid, const QPixmap &img); + void updateRoomAvatar(const QString &roomid, const QString &img); void highlightSelectedRoom(const QString &room_id); void updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount); void updateRoomDescription(const QString &roomid, const DescInfo &info); void closeJoinRoomDialog(bool isJoining, QString roomAlias); void updateReadStatus(const std::map &status); + void nextRoom(); + void previousRoom(); protected: void paintEvent(QPaintEvent *event) override; diff --git a/src/RunGuard.cpp b/src/RunGuard.cpp deleted file mode 100644 index 75833eb7..00000000 --- a/src/RunGuard.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "RunGuard.h" - -#include - -namespace { - -QString -generateKeyHash(const QString &key, const QString &salt) -{ - QByteArray data; - - data.append(key.toUtf8()); - data.append(salt.toUtf8()); - data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex(); - - return data; -} -} - -RunGuard::RunGuard(const QString &key) - : key(key) - , memLockKey(generateKeyHash(key, "_memLockKey")) - , sharedmemKey(generateKeyHash(key, "_sharedmemKey")) - , sharedMem(sharedmemKey) - , memLock(memLockKey, 1) -{ - memLock.acquire(); - { - // Fix for *nix: http://habrahabr.ru/post/173281/ - QSharedMemory fix(sharedmemKey); - fix.attach(); - } - - memLock.release(); -} - -RunGuard::~RunGuard() { release(); } - -bool -RunGuard::isAnotherRunning() -{ - if (sharedMem.isAttached()) - return false; - - memLock.acquire(); - const bool isRunning = sharedMem.attach(); - - if (isRunning) - sharedMem.detach(); - - memLock.release(); - - return isRunning; -} - -bool -RunGuard::tryToRun() -{ - // Extra check - if (isAnotherRunning()) - return false; - - memLock.acquire(); - const bool result = sharedMem.create(sizeof(quint64)); - memLock.release(); - - if (!result) { - release(); - return false; - } - - return true; -} - -void -RunGuard::release() -{ - memLock.acquire(); - - if (sharedMem.isAttached()) - sharedMem.detach(); - - memLock.release(); -} diff --git a/src/RunGuard.h b/src/RunGuard.h deleted file mode 100644 index f9a9641a..00000000 --- a/src/RunGuard.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -// -// Taken from -// https://stackoverflow.com/questions/5006547/qt-best-practice-for-a-single-instance-app-protection -// - -#include -#include -#include - -class RunGuard -{ -public: - RunGuard(const QString &key); - ~RunGuard(); - - bool isAnotherRunning(); - bool tryToRun(); - void release(); - -private: - const QString key; - const QString memLockKey; - const QString sharedmemKey; - - QSharedMemory sharedMem; - QSystemSemaphore memLock; - - Q_DISABLE_COPY(RunGuard) -}; diff --git a/src/SideBarActions.cpp b/src/SideBarActions.cpp index 2f447cd8..4934ec05 100644 --- a/src/SideBarActions.cpp +++ b/src/SideBarActions.cpp @@ -1,15 +1,15 @@ -#include #include +#include +#include #include #include "Config.h" #include "MainWindow.h" #include "SideBarActions.h" -#include "Utils.h" +#include "Splitter.h" #include "ui/FlatButton.h" #include "ui/Menu.h" -#include "ui/OverlayModal.h" SideBarActions::SideBarActions(QWidget *parent) : QWidget{parent} @@ -93,7 +93,7 @@ SideBarActions::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); - const auto sidebarSizes = utils::calculateSidebarSizes(QFont{}); + const auto sidebarSizes = splitter::calculateSidebarSizes(QFont{}); if (width() <= sidebarSizes.small) { roomDirectory_->hide(); diff --git a/src/SideBarActions.h b/src/SideBarActions.h index ce96cba8..662750b3 100644 --- a/src/SideBarActions.h +++ b/src/SideBarActions.h @@ -2,7 +2,6 @@ #include #include -#include #include namespace mtx { @@ -13,6 +12,7 @@ struct CreateRoom; class Menu; class FlatButton; +class QResizeEvent; class SideBarActions : public QWidget { diff --git a/src/Splitter.cpp b/src/Splitter.cpp index ddb1dc1c..04375853 100644 --- a/src/Splitter.cpp +++ b/src/Splitter.cpp @@ -15,24 +15,19 @@ * along with this program. If not, see . */ -#include -#include -#include #include -#include -#include "Config.h" +#include "Logging.h" #include "Splitter.h" constexpr auto MaxWidth = (1 << 24) - 1; Splitter::Splitter(QWidget *parent) : QSplitter(parent) - , sz_{utils::calculateSidebarSizes(QFont{})} + , sz_{splitter::calculateSidebarSizes(QFont{})} { connect(this, &QSplitter::splitterMoved, this, &Splitter::onSplitterMoved); setChildrenCollapsible(false); - setStyleSheet("QSplitter::handle { image: none; }"); } void @@ -80,7 +75,7 @@ Splitter::onSplitterMoved(int pos, int index) auto s = sizes(); if (s.count() < 2) { - qWarning() << "Splitter needs at least two children"; + nhlog::ui()->warn("Splitter needs at least two children"); return; } @@ -165,3 +160,17 @@ Splitter::showFullRoomList() left->show(); left->setMaximumWidth(MaxWidth); } + +splitter::SideBarSizes +splitter::calculateSidebarSizes(const QFont &f) +{ + const auto height = static_cast(QFontMetrics{f}.lineSpacing()); + + SideBarSizes sz; + sz.small = std::ceil(3.8 * height); + sz.normal = std::ceil(16 * height); + sz.groups = std::ceil(3 * height); + sz.collapsePoint = 2 * sz.normal; + + return sz; +} diff --git a/src/Splitter.h b/src/Splitter.h index 14d6773e..7bde89de 100644 --- a/src/Splitter.h +++ b/src/Splitter.h @@ -17,15 +17,27 @@ #pragma once -#include "Utils.h" #include +namespace splitter { +struct SideBarSizes +{ + int small; + int normal; + int groups; + int collapsePoint; +}; + +SideBarSizes +calculateSidebarSizes(const QFont &f); +} + class Splitter : public QSplitter { Q_OBJECT public: explicit Splitter(QWidget *parent = nullptr); - ~Splitter(); + ~Splitter() override; void restoreSizes(int fallback); @@ -45,5 +57,5 @@ private: int leftMoveCount_ = 0; int rightMoveCount_ = 0; - utils::SideBarSizes sz_; + splitter::SideBarSizes sz_; }; diff --git a/src/SuggestionsPopup.cpp b/src/SuggestionsPopup.cpp deleted file mode 100644 index 952d2ef3..00000000 --- a/src/SuggestionsPopup.cpp +++ /dev/null @@ -1,296 +0,0 @@ -#include -#include -#include - -#include "Config.h" -#include "SuggestionsPopup.h" -#include "Utils.h" -#include "ui/Avatar.h" -#include "ui/DropShadow.h" - -constexpr int PopupHMargin = 4; -constexpr int PopupItemMargin = 3; - -PopupItem::PopupItem(QWidget *parent) - : QWidget(parent) - , avatar_{new Avatar(this)} - , hovering_{false} -{ - setMouseTracking(true); - setAttribute(Qt::WA_Hover); - - topLayout_ = new QHBoxLayout(this); - topLayout_->setContentsMargins( - PopupHMargin, PopupItemMargin, PopupHMargin, PopupItemMargin); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); -} - -void -PopupItem::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - - if (underMouse() || hovering_) - p.fillRect(rect(), hoverColor_); -} - -UserItem::UserItem(QWidget *parent, const QString &user_id) - : PopupItem(parent) - , userId_{user_id} -{ - auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), userId_); - - avatar_->setSize(conf::popup::avatar); - avatar_->setLetter(utils::firstChar(displayName)); - - // If it's a matrix id we use the second letter. - if (displayName.size() > 1 && displayName.at(0) == '@') - avatar_->setLetter(QChar(displayName.at(1))); - - userName_ = new QLabel(displayName, this); - - topLayout_->addWidget(avatar_); - topLayout_->addWidget(userName_, 1); - - resolveAvatar(user_id); -} - -void -UserItem::updateItem(const QString &user_id) -{ - userId_ = user_id; - - auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), userId_); - - // If it's a matrix id we use the second letter. - if (displayName.size() > 1 && displayName.at(0) == '@') - avatar_->setLetter(QChar(displayName.at(1))); - else - avatar_->setLetter(utils::firstChar(displayName)); - - userName_->setText(displayName); - resolveAvatar(user_id); -} - -void -UserItem::resolveAvatar(const QString &user_id) -{ - AvatarProvider::resolve( - ChatPage::instance()->currentRoom(), userId_, this, [this, user_id](const QImage &img) { - // The user on the widget when the avatar is resolved, - // might be different from the user that made the call. - if (user_id == userId_) - avatar_->setImage(img); - else - // We try to resolve the avatar again. - resolveAvatar(userId_); - }); -} - -void -UserItem::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() != Qt::RightButton) - emit clicked( - Cache::displayName(ChatPage::instance()->currentRoom(), selectedText())); - - QWidget::mousePressEvent(event); -} - -RoomItem::RoomItem(QWidget *parent, const RoomSearchResult &res) - : PopupItem(parent) - , roomId_{QString::fromStdString(res.room_id)} -{ - auto name = QFontMetrics(QFont()).elidedText( - QString::fromStdString(res.info.name), Qt::ElideRight, parentWidget()->width() - 10); - - avatar_->setSize(conf::popup::avatar + 6); - avatar_->setLetter(utils::firstChar(name)); - - roomName_ = new QLabel(name, this); - roomName_->setMargin(0); - - topLayout_->addWidget(avatar_); - topLayout_->addWidget(roomName_, 1); - - if (!res.img.isNull()) - avatar_->setImage(res.img); -} - -void -RoomItem::updateItem(const RoomSearchResult &result) -{ - roomId_ = QString::fromStdString(std::move(result.room_id)); - - auto name = - QFontMetrics(QFont()).elidedText(QString::fromStdString(std::move(result.info.name)), - Qt::ElideRight, - parentWidget()->width() - 10); - - roomName_->setText(name); - - if (!result.img.isNull()) - avatar_->setImage(result.img); - else - avatar_->setLetter(utils::firstChar(name)); -} - -void -RoomItem::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() != Qt::RightButton) - emit clicked(selectedText()); - - QWidget::mousePressEvent(event); -} - -SuggestionsPopup::SuggestionsPopup(QWidget *parent) - : QWidget(parent) -{ - setAttribute(Qt::WA_ShowWithoutActivating, true); - setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); - - layout_ = new QVBoxLayout(this); - layout_->setMargin(0); - layout_->setSpacing(0); -} - -void -SuggestionsPopup::addRooms(const std::vector &rooms) -{ - if (rooms.empty()) { - hide(); - return; - } - - const size_t layoutCount = layout_->count(); - const size_t roomCount = rooms.size(); - - // Remove the extra widgets from the layout. - if (roomCount < layoutCount) - removeLayoutItemsAfter(roomCount - 1); - - for (size_t i = 0; i < roomCount; ++i) { - auto item = layout_->itemAt(i); - - // Create a new widget if there isn't already one in that - // layout position. - if (!item) { - auto room = new RoomItem(this, rooms.at(i)); - connect(room, &RoomItem::clicked, this, &SuggestionsPopup::itemSelected); - layout_->addWidget(room); - } else { - // Update the current widget with the new data. - auto room = qobject_cast(item->widget()); - if (room) - room->updateItem(rooms.at(i)); - } - } - - resetSelection(); - adjustSize(); - - resize(geometry().width(), 40 * rooms.size()); - - selectNextSuggestion(); -} - -void -SuggestionsPopup::addUsers(const QVector &users) -{ - if (users.isEmpty()) { - hide(); - return; - } - - const size_t layoutCount = layout_->count(); - const size_t userCount = users.size(); - - // Remove the extra widgets from the layout. - if (userCount < layoutCount) - removeLayoutItemsAfter(userCount - 1); - - for (size_t i = 0; i < userCount; ++i) { - auto item = layout_->itemAt(i); - - // Create a new widget if there isn't already one in that - // layout position. - if (!item) { - auto user = new UserItem(this, users.at(i).user_id); - connect(user, &UserItem::clicked, this, &SuggestionsPopup::itemSelected); - layout_->addWidget(user); - } else { - // Update the current widget with the new data. - auto userWidget = qobject_cast(item->widget()); - if (userWidget) - userWidget->updateItem(users.at(i).user_id); - } - } - - resetSelection(); - adjustSize(); - - selectNextSuggestion(); -} - -void -SuggestionsPopup::hoverSelection() -{ - resetHovering(); - setHovering(selectedItem_); - update(); -} - -void -SuggestionsPopup::selectNextSuggestion() -{ - selectedItem_++; - if (selectedItem_ >= layout_->count()) - selectFirstItem(); - - hoverSelection(); -} - -void -SuggestionsPopup::selectPreviousSuggestion() -{ - selectedItem_--; - if (selectedItem_ < 0) - selectLastItem(); - - hoverSelection(); -} - -void -SuggestionsPopup::resetHovering() -{ - for (int i = 0; i < layout_->count(); ++i) { - const auto item = qobject_cast(layout_->itemAt(i)->widget()); - - if (item) - item->setHovering(false); - } -} - -void -SuggestionsPopup::setHovering(int pos) -{ - const auto &item = layout_->itemAt(pos); - const auto &widget = qobject_cast(item->widget()); - - if (widget) - widget->setHovering(true); -} - -void -SuggestionsPopup::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/src/SuggestionsPopup.h b/src/SuggestionsPopup.h deleted file mode 100644 index 72d6c7eb..00000000 --- a/src/SuggestionsPopup.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "AvatarProvider.h" -#include "Cache.h" -#include "ChatPage.h" - -class Avatar; -struct SearchResult; - -class PopupItem : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor) - Q_PROPERTY(bool hovering READ hovering WRITE setHovering) - -public: - PopupItem(QWidget *parent); - - QString selectedText() const { return QString(); } - QColor hoverColor() const { return hoverColor_; } - void setHoverColor(QColor &color) { hoverColor_ = color; } - - bool hovering() const { return hovering_; } - void setHovering(const bool hover) { hovering_ = hover; }; - -protected: - void paintEvent(QPaintEvent *event) override; - -signals: - void clicked(const QString &text); - -protected: - QHBoxLayout *topLayout_; - Avatar *avatar_; - QColor hoverColor_; - - //! Set if the item is currently being - //! hovered during tab completion (cycling). - bool hovering_; -}; - -class UserItem : public PopupItem -{ - Q_OBJECT - -public: - UserItem(QWidget *parent, const QString &user_id); - QString selectedText() const { return userId_; } - void updateItem(const QString &user_id); - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - void resolveAvatar(const QString &user_id); - - QLabel *userName_; - QString userId_; -}; - -class RoomItem : public PopupItem -{ - Q_OBJECT - -public: - RoomItem(QWidget *parent, const RoomSearchResult &res); - QString selectedText() const { return roomId_; } - void updateItem(const RoomSearchResult &res); - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - QLabel *roomName_; - QString roomId_; - RoomSearchResult info_; -}; - -class SuggestionsPopup : public QWidget -{ - Q_OBJECT - -public: - explicit SuggestionsPopup(QWidget *parent = nullptr); - - template - void selectHoveredSuggestion() - { - const auto item = layout_->itemAt(selectedItem_); - if (!item) - return; - - const auto &widget = qobject_cast(item->widget()); - emit itemSelected( - Cache::displayName(ChatPage::instance()->currentRoom(), widget->selectedText())); - - resetSelection(); - } - -public slots: - void addUsers(const QVector &users); - void addRooms(const std::vector &rooms); - - //! Move to the next available suggestion item. - void selectNextSuggestion(); - //! Move to the previous available suggestion item. - void selectPreviousSuggestion(); - //! Remove hovering from all items. - void resetHovering(); - //! Set hovering to the item in the given layout position. - void setHovering(int pos); - -protected: - void paintEvent(QPaintEvent *event) override; - -signals: - void itemSelected(const QString &user); - -private: - void hoverSelection(); - void resetSelection() { selectedItem_ = -1; } - void selectFirstItem() { selectedItem_ = 0; } - void selectLastItem() { selectedItem_ = layout_->count() - 1; } - void removeLayoutItemsAfter(size_t startingPos) - { - size_t posToRemove = layout_->count() - 1; - - QLayoutItem *item; - while (startingPos <= posToRemove && (item = layout_->takeAt(posToRemove)) != 0) { - delete item->widget(); - delete item; - - posToRemove = layout_->count() - 1; - } - } - - QVBoxLayout *layout_; - - //! Counter for tab completion (cycling). - int selectedItem_ = -1; -}; diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 934f2b2c..11f7ddda 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -16,12 +16,9 @@ */ #include -#include #include #include -#include #include -#include #include #include #include @@ -31,7 +28,7 @@ #include "Cache.h" #include "ChatPage.h" -#include "Config.h" +#include "Logging.h" #include "TextInputWidget.h" #include "Utils.h" #include "ui/FlatButton.h" @@ -48,7 +45,8 @@ static constexpr int ButtonHeight = 22; FilteredTextEdit::FilteredTextEdit(QWidget *parent) : QTextEdit{parent} , history_index_{0} - , popup_{parent} + , suggestionsPopup_{parent} + , replyPopup_{parent} , previewDialog_{parent} { setFrameStyle(QFrame::NoFrame); @@ -75,36 +73,43 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent) &FilteredTextEdit::uploadData); connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults); - connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { - popup_.hide(); - - auto cursor = textCursor(); - const int end = cursor.position(); - - cursor.setPosition(atTriggerPosition_, QTextCursor::MoveAnchor); - cursor.setPosition(end, QTextCursor::KeepAnchor); - cursor.removeSelectedText(); - cursor.insertText(text); + connect(&replyPopup_, &ReplyPopup::userSelected, this, [](const QString &text) { + // TODO: Show user avatar window. + nhlog::ui()->info("User selected: " + text.toStdString()); }); + connect( + &suggestionsPopup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { + suggestionsPopup_.hide(); + + auto cursor = textCursor(); + const int end = cursor.position(); + + cursor.setPosition(atTriggerPosition_, QTextCursor::MoveAnchor); + cursor.setPosition(end, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + cursor.insertText(text); + }); + + connect(&replyPopup_, &ReplyPopup::cancel, this, [this]() { closeReply(); }); // For cycling through the suggestions by hitting tab. connect(this, &FilteredTextEdit::selectNextSuggestion, - &popup_, + &suggestionsPopup_, &SuggestionsPopup::selectNextSuggestion); connect(this, &FilteredTextEdit::selectPreviousSuggestion, - &popup_, + &suggestionsPopup_, &SuggestionsPopup::selectPreviousSuggestion); connect(this, &FilteredTextEdit::selectHoveredSuggestion, this, [this]() { - popup_.selectHoveredSuggestion(); + suggestionsPopup_.selectHoveredSuggestion(); }); previewDialog_.hide(); } void -FilteredTextEdit::showResults(const QVector &results) +FilteredTextEdit::showResults(const std::vector &results) { QPoint pos; @@ -117,9 +122,9 @@ FilteredTextEdit::showResults(const QVector &results) pos = viewport()->mapToGlobal(rect.topLeft()); } - popup_.addUsers(results); - popup_.move(pos.x(), pos.y() - popup_.height() - 10); - popup_.show(); + suggestionsPopup_.addUsers(results); + suggestionsPopup_.move(pos.x(), pos.y() - suggestionsPopup_.height() - 10); + suggestionsPopup_.show(); } void @@ -146,7 +151,7 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) closeSuggestions(); } - if (popup_.isVisible()) { + if (suggestionsPopup_.isVisible()) { switch (event->key()) { case Qt::Key_Down: case Qt::Key_Tab: @@ -169,6 +174,17 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) } } + if (replyPopup_.isVisible()) { + switch (event->key()) { + case Qt::Key_Escape: + closeReply(); + return; + + default: + break; + } + } + switch (event->key()) { case Qt::Key_At: atTriggerPosition_ = textCursor().position(); @@ -202,6 +218,7 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) if (!(event->modifiers() & Qt::ShiftModifier)) { stopTyping(); submit(); + closeReply(); } else { QTextEdit::keyPressEvent(event); } @@ -286,8 +303,9 @@ FilteredTextEdit::insertFromMimeData(const QMimeData *source) const auto audio = formats.filter("audio/", Qt::CaseInsensitive); const auto video = formats.filter("video/", Qt::CaseInsensitive); - if (!image.empty()) { - showPreview(source, image); + if (source->hasImage()) { + QImage img = qvariant_cast(source->imageData()); + previewDialog_.setPreview(img, image.front()); } else if (!audio.empty()) { showPreview(source, audio); } else if (!video.empty()) { @@ -398,17 +416,30 @@ FilteredTextEdit::submit() auto name = text.mid(1, command_end - 1); auto args = text.mid(command_end + 1); if (name.isEmpty() || name == "/") { - message(args); + message(args, related); } else { command(name, args); } } else { - message(std::move(text)); + message(std::move(text), std::move(related)); } + related = {}; + clear(); } +void +FilteredTextEdit::showReplyPopup(const RelatedInfo &related_) +{ + QPoint pos = viewport()->mapToGlobal(this->pos()); + + replyPopup_.setReplyContent(related_); + replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10); + replyPopup_.setFixedWidth(this->parentWidget()->width()); + replyPopup_.show(); +} + void FilteredTextEdit::textChanged() { @@ -416,21 +447,18 @@ FilteredTextEdit::textChanged() } void -FilteredTextEdit::uploadData(const QByteArray data, const QString &media, const QString &filename) +FilteredTextEdit::uploadData(const QByteArray data, + const QString &mediaType, + const QString &filename) { QSharedPointer buffer{new QBuffer{this}}; buffer->setData(data); emit startedUpload(); - if (media == "image") - emit image(buffer, filename); - else if (media == "audio") - emit audio(buffer, filename); - else if (media == "video") - emit video(buffer, filename); - else - emit file(buffer, filename); + emit media(buffer, mediaType, filename, related); + related = {}; + closeReply(); } void @@ -492,15 +520,15 @@ TextInputWidget::TextInputWidget(QWidget *parent) emit heightChanged(widgetHeight); }); connect(input_, &FilteredTextEdit::showSuggestions, this, [this](const QString &q) { - if (q.isEmpty() || !cache::client()) + if (q.isEmpty()) return; QtConcurrent::run([this, q = q.toLower().toStdString()]() { try { - emit input_->resultsRetrieved(cache::client()->searchUsers( + emit input_->resultsRetrieved(cache::searchUsers( ChatPage::instance()->currentRoom().toStdString(), q)); } catch (const lmdb::error &e) { - std::cout << e.what() << '\n'; + nhlog::db()->error("Suggestion retrieval failed: {}", e.what()); } }); }); @@ -537,10 +565,7 @@ TextInputWidget::TextInputWidget(QWidget *parent) connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage); connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command); - connect(input_, &FilteredTextEdit::image, this, &TextInputWidget::uploadImage); - connect(input_, &FilteredTextEdit::audio, this, &TextInputWidget::uploadAudio); - connect(input_, &FilteredTextEdit::video, this, &TextInputWidget::uploadVideo); - connect(input_, &FilteredTextEdit::file, this, &TextInputWidget::uploadFile); + connect(input_, &FilteredTextEdit::media, this, &TextInputWidget::uploadMedia); connect(emojiBtn_, SIGNAL(emojiSelected(const QString &)), this, @@ -574,21 +599,36 @@ void TextInputWidget::command(QString command, QString args) { if (command == "me") { - sendEmoteMessage(args); + sendEmoteMessage(args, input_->related); } else if (command == "join") { sendJoinRoomRequest(args); + } else if (command == "invite") { + sendInviteRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "kick") { + sendKickRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "ban") { + sendBanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "unban") { + sendUnbanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == "shrug") { - sendTextMessage("¯\\_(ツ)_/¯"); + sendTextMessage("¯\\_(ツ)_/¯", input_->related); } else if (command == "fliptable") { - sendTextMessage("(╯°□°)╯︵ ┻━┻"); + sendTextMessage("(╯°□°)╯︵ ┻━┻", input_->related); + } else if (command == "unfliptable") { + sendTextMessage(" ┯━┯╭( º _ º╭)", input_->related); + } else if (command == "sovietflip") { + sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\", input_->related); } + + input_->related = std::nullopt; } void TextInputWidget::openFileSelection() { + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); const auto fileName = - QFileDialog::getOpenFileName(this, tr("Select a file"), "", tr("All Files (*)")); + QFileDialog::getOpenFileName(this, tr("Select a file"), homeFolder, tr("All Files (*)")); if (fileName.isEmpty()) return; @@ -599,14 +639,10 @@ TextInputWidget::openFileSelection() const auto format = mime.name().split("/")[0]; QSharedPointer file{new QFile{fileName, this}}; - if (format == "image") - emit uploadImage(file, fileName); - else if (format == "audio") - emit uploadAudio(file, fileName); - else if (format == "video") - emit uploadVideo(file, fileName); - else - emit uploadFile(file, fileName); + + emit uploadMedia(file, format, QFileInfo(fileName).fileName(), input_->related); + input_->related = {}; + input_->closeReply(); showUploadSpinner(); } @@ -653,12 +689,14 @@ TextInputWidget::paintEvent(QPaintEvent *) } void -TextInputWidget::addReply(const QString &username, const QString &msg) +TextInputWidget::addReply(const RelatedInfo &related) { - input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg)); + // input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg)); input_->setFocus(); + // input_->showReplyPopup(related); auto cursor = input_->textCursor(); cursor.movePosition(QTextCursor::End); input_->setTextCursor(cursor); + input_->related = related; } diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index 8f634f6b..77d77e44 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -18,23 +18,18 @@ #pragma once #include -#include -#include +#include -#include -#include +#include #include #include #include #include -#include "SuggestionsPopup.h" #include "dialogs/PreviewUploadOverlay.h" #include "emoji/PickButton.h" - -namespace dialogs { -class PreviewUploadOverlay; -} +#include "popups/ReplyPopup.h" +#include "popups/SuggestionsPopup.h" struct SearchResult; @@ -54,28 +49,37 @@ public: QSize minimumSizeHint() const override; void submit(); + void showReplyPopup(const RelatedInfo &related_); + void closeReply() + { + replyPopup_.hide(); + related = {}; + } + + // Used for replies + std::optional related; signals: void heightChanged(int height); void startedTyping(); void stoppedTyping(); void startedUpload(); - void message(QString); + void message(QString, const std::optional &); void command(QString name, QString args); - void image(QSharedPointer data, const QString &filename); - void audio(QSharedPointer data, const QString &filename); - void video(QSharedPointer data, const QString &filename); - void file(QSharedPointer data, const QString &filename); + void media(QSharedPointer data, + QString mimeClass, + const QString &filename, + const std::optional &related); //! Trigger the suggestion popup. void showSuggestions(const QString &query); - void resultsRetrieved(const QVector &results); + void resultsRetrieved(const std::vector &results); void selectNextSuggestion(); void selectPreviousSuggestion(); void selectHoveredSuggestion(); public slots: - void showResults(const QVector &results); + void showResults(const std::vector &results); protected: void keyPressEvent(QKeyEvent *event) override; @@ -83,7 +87,7 @@ protected: void insertFromMimeData(const QMimeData *source) override; void focusOutEvent(QFocusEvent *event) override { - popup_.hide(); + suggestionsPopup_.hide(); QTextEdit::focusOutEvent(event); } @@ -92,7 +96,8 @@ private: size_t history_index_; QTimer *typingTimer_; - SuggestionsPopup popup_; + SuggestionsPopup suggestionsPopup_; + ReplyPopup replyPopup_; enum class AnchorType { @@ -104,7 +109,7 @@ private: int anchorWidth(AnchorType anchor) { return static_cast(anchor); } - void closeSuggestions() { popup_.hide(); } + void closeSuggestions() { suggestionsPopup_.hide(); } void resetAnchor() { atTriggerPosition_ = -1; } bool isAnchorValid() { return atTriggerPosition_ != -1; } bool hasAnchor(int pos, AnchorType anchor) @@ -137,7 +142,7 @@ class TextInputWidget : public QWidget Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor) public: - TextInputWidget(QWidget *parent = 0); + TextInputWidget(QWidget *parent = nullptr); void stopTyping(); @@ -158,22 +163,27 @@ public slots: void openFileSelection(); void hideUploadSpinner(); void focusLineEdit() { input_->setFocus(); } - void addReply(const QString &username, const QString &msg); + void addReply(const RelatedInfo &related); + void closeReplyPopup() { input_->closeReply(); } private slots: void addSelectedEmoji(const QString &emoji); signals: - void sendTextMessage(QString msg); - void sendEmoteMessage(QString msg); + void sendTextMessage(const QString &msg, const std::optional &related); + void sendEmoteMessage(QString msg, const std::optional &related); void heightChanged(int height); - void uploadImage(const QSharedPointer data, const QString &filename); - void uploadFile(const QSharedPointer data, const QString &filename); - void uploadAudio(const QSharedPointer data, const QString &filename); - void uploadVideo(const QSharedPointer data, const QString &filename); + void uploadMedia(const QSharedPointer data, + QString mimeClass, + const QString &filename, + const std::optional &related); void sendJoinRoomRequest(const QString &room); + void sendInviteRoomRequest(const QString &userid, const QString &reason); + void sendKickRoomRequest(const QString &userid, const QString &reason); + void sendBanRoomRequest(const QString &userid, const QString &reason); + void sendUnbanRoomRequest(const QString &userid, const QString &reason); void startedTyping(); void stoppedTyping(); diff --git a/src/TopRoomBar.cpp b/src/TopRoomBar.cpp index 5c817dc2..ffd57d50 100644 --- a/src/TopRoomBar.cpp +++ b/src/TopRoomBar.cpp @@ -15,8 +15,16 @@ * along with this program. If not, see . */ -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include "Config.h" #include "MainWindow.h" @@ -46,9 +54,8 @@ TopRoomBar::TopRoomBar(QWidget *parent) topLayout_->setContentsMargins( 2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin); - avatar_ = new Avatar(this); + avatar_ = new Avatar(this, fontHeight * 2); avatar_->setLetter(""); - avatar_->setSize(fontHeight * 2); textLayout_ = new QVBoxLayout(); textLayout_->setSpacing(0); @@ -80,11 +87,21 @@ TopRoomBar::TopRoomBar(QWidget *parent) settingsBtn_->setFixedSize(buttonSize_, buttonSize_); settingsBtn_->setCornerRadius(buttonSize_ / 2); + mentionsBtn_ = new FlatButton(this); + mentionsBtn_->setToolTip(tr("Mentions")); + mentionsBtn_->setFixedSize(buttonSize_, buttonSize_); + mentionsBtn_->setCornerRadius(buttonSize_ / 2); + QIcon settings_icon; settings_icon.addFile(":/icons/icons/ui/vertical-ellipsis.png"); settingsBtn_->setIcon(settings_icon); settingsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2)); + QIcon mentions_icon; + mentions_icon.addFile(":/icons/icons/ui/at-solid.svg"); + mentionsBtn_->setIcon(mentions_icon); + mentionsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2)); + backBtn_ = new FlatButton(this); backBtn_->setFixedSize(buttonSize_, buttonSize_); backBtn_->setCornerRadius(buttonSize_ / 2); @@ -100,6 +117,7 @@ TopRoomBar::TopRoomBar(QWidget *parent) topLayout_->addWidget(avatar_); topLayout_->addWidget(backBtn_); topLayout_->addLayout(textLayout_, 1); + topLayout_->addWidget(mentionsBtn_, 0, Qt::AlignRight); topLayout_->addWidget(settingsBtn_, 0, Qt::AlignRight); menu_ = new Menu(this); @@ -135,6 +153,11 @@ TopRoomBar::TopRoomBar(QWidget *parent) menu_->popup( QPoint(pos.x() + buttonSize_ - menu_->sizeHint().width(), pos.y() + buttonSize_)); }); + + connect(mentionsBtn_, &QPushButton::clicked, this, [this]() { + auto pos = mapToGlobal(mentionsBtn_->pos()); + emit mentionsClicked(pos); + }); } void @@ -167,7 +190,7 @@ TopRoomBar::reset() } void -TopRoomBar::updateRoomAvatar(const QImage &avatar_image) +TopRoomBar::updateRoomAvatar(const QString &avatar_image) { avatar_->setImage(avatar_image); update(); @@ -195,3 +218,19 @@ TopRoomBar::updateRoomTopic(QString topic) topicLabel_->setHtml(topic); update(); } + +void +TopRoomBar::mousePressEvent(QMouseEvent *) +{ + if (roomSettings_ != nullptr) + roomSettings_->trigger(); +} + +void +TopRoomBar::paintEvent(QPaintEvent *) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/src/TopRoomBar.h b/src/TopRoomBar.h index 5b7d3344..63ce847e 100644 --- a/src/TopRoomBar.h +++ b/src/TopRoomBar.h @@ -17,16 +17,9 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include class Avatar; class FlatButton; @@ -34,6 +27,12 @@ class Menu; class TextLabel; class OverlayModal; +class QPainter; +class QLabel; +class QIcon; +class QHBoxLayout; +class QVBoxLayout; + class TopRoomBar : public QWidget { Q_OBJECT @@ -41,9 +40,9 @@ class TopRoomBar : public QWidget Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor) public: - TopRoomBar(QWidget *parent = 0); + TopRoomBar(QWidget *parent = nullptr); - void updateRoomAvatar(const QImage &avatar_image); + void updateRoomAvatar(const QString &avatar_image); void updateRoomAvatar(const QIcon &icon); void updateRoomName(const QString &name); void updateRoomTopic(QString topic); @@ -63,21 +62,11 @@ public slots: signals: void inviteUsers(QStringList users); void showRoomList(); + void mentionsClicked(const QPoint &pos); protected: - void mousePressEvent(QMouseEvent *) override - { - if (roomSettings_ != nullptr) - roomSettings_->trigger(); - } - - void paintEvent(QPaintEvent *) override - { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - } + void mousePressEvent(QMouseEvent *) override; + void paintEvent(QPaintEvent *) override; private: QHBoxLayout *topLayout_ = nullptr; @@ -93,6 +82,7 @@ private: QAction *inviteUsers_ = nullptr; FlatButton *settingsBtn_; + FlatButton *mentionsBtn_; FlatButton *backBtn_; Avatar *avatar_; diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp index e7348b89..6ab011d1 100644 --- a/src/TrayIcon.cpp +++ b/src/TrayIcon.cpp @@ -15,9 +15,11 @@ * along with this program. If not, see . */ +#include #include #include #include +#include #include #include "TrayIcon.h" @@ -134,12 +136,16 @@ TrayIcon::setUnreadCount(int count) { // Use the native badge counter in MacOS. #if defined(Q_OS_MAC) +// currently, to avoid writing obj-c code, ignore deprecated warnings on the badge functions +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" auto labelText = count == 0 ? "" : QString::number(count); if (labelText == QtMac::badgeLabelText()) return; QtMac::setBadgeLabelText(labelText); +#pragma clang diagnostic pop #elif defined(Q_OS_WIN) // FIXME: Find a way to use Windows apis for the badge counter (if any). #else diff --git a/src/TrayIcon.h b/src/TrayIcon.h index a3536cc3..24ac81da 100644 --- a/src/TrayIcon.h +++ b/src/TrayIcon.h @@ -17,22 +17,23 @@ #pragma once -#include #include #include -#include #include #include +class QAction; +class QPainter; + class MsgCountComposedIcon : public QIconEngine { public: MsgCountComposedIcon(const QString &filename); - virtual void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state); - virtual QIconEngine *clone() const; - virtual QList availableSizes(QIcon::Mode mode, QIcon::State state) const; - virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + QIconEngine *clone() const override; + QList availableSizes(QIcon::Mode mode, QIcon::State state) const override; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; int msgCount = 0; diff --git a/src/TypingDisplay.cpp b/src/TypingDisplay.cpp deleted file mode 100644 index 11313adc..00000000 --- a/src/TypingDisplay.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include -#include - -#include "Config.h" -#include "TypingDisplay.h" -#include "ui/Painter.h" - -constexpr int LEFT_PADDING = 24; -constexpr int RECT_PADDING = 2; - -TypingDisplay::TypingDisplay(QWidget *parent) - : OverlayWidget(parent) - , offset_{conf::textInput::height} -{ - setFixedHeight(QFontMetrics(font()).height() + RECT_PADDING); - setAttribute(Qt::WA_TransparentForMouseEvents); -} - -void -TypingDisplay::setOffset(int margin) -{ - offset_ = margin; - move(0, parentWidget()->height() - offset_ - height()); -} - -void -TypingDisplay::setUsers(const QStringList &uid) -{ - move(0, parentWidget()->height() - offset_ - height()); - - text_.clear(); - - if (uid.isEmpty()) { - hide(); - update(); - - return; - } - - text_ = uid.join(", "); - - if (uid.size() == 1) - text_ += tr(" is typing"); - else if (uid.size() > 1) - text_ += tr(" are typing"); - - show(); - update(); -} - -void -TypingDisplay::paintEvent(QPaintEvent *) -{ - Painter p(this); - PainterHighQualityEnabler hq(p); - - QFont f; - f.setPointSizeF(f.pointSizeF() * 0.9); - - p.setFont(f); - p.setPen(QPen(textColor())); - - QRect region = rect(); - region.translate(LEFT_PADDING, 0); - - QFontMetrics fm(f); - text_ = fm.elidedText(text_, Qt::ElideRight, (double)(width() * 0.75)); - - QPainterPath path; - path.addRoundedRect(QRectF(0, 0, fm.width(text_) + 2 * LEFT_PADDING, height()), 3, 3); - - p.fillPath(path, backgroundColor()); - p.drawText(region, Qt::AlignVCenter, text_); -} diff --git a/src/TypingDisplay.h b/src/TypingDisplay.h deleted file mode 100644 index 332d9c66..00000000 --- a/src/TypingDisplay.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "ui/OverlayWidget.h" - -class QPaintEvent; - -class TypingDisplay : public OverlayWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) - Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) - -public: - TypingDisplay(QWidget *parent = nullptr); - - void setUsers(const QStringList &user_ids); - - void setTextColor(const QColor &color) { textColor_ = color; }; - QColor textColor() const { return textColor_; }; - - void setBackgroundColor(const QColor &color) { bgColor_ = color; }; - QColor backgroundColor() const { return bgColor_; }; - -public slots: - void setOffset(int margin); - -protected: - void paintEvent(QPaintEvent *event) override; - -private: - int offset_; - QColor textColor_; - QColor bgColor_; - QString text_; -}; diff --git a/src/UserInfoWidget.cpp b/src/UserInfoWidget.cpp index 5345fb2a..2e21d41f 100644 --- a/src/UserInfoWidget.cpp +++ b/src/UserInfoWidget.cpp @@ -1,3 +1,4 @@ + /* * nheko Copyright (C) 2017 Konstantinos Sideris * @@ -15,14 +16,15 @@ * along with this program. If not, see . */ +#include #include #include #include "Config.h" #include "MainWindow.h" +#include "Splitter.h" #include "UserInfoWidget.h" -#include "Utils.h" #include "ui/Avatar.h" #include "ui/FlatButton.h" #include "ui/OverlayModal.h" @@ -52,10 +54,9 @@ UserInfoWidget::UserInfoWidget(QWidget *parent) textLayout_->setSpacing(widgetMargin / 2); textLayout_->setContentsMargins(widgetMargin * 2, widgetMargin, widgetMargin, widgetMargin); - userAvatar_ = new Avatar(this); + userAvatar_ = new Avatar(this, fontHeight * 2.5); userAvatar_->setObjectName("userAvatar"); userAvatar_->setLetter(QChar('?')); - userAvatar_->setSize(fontHeight * 2.5); QFont nameFont; nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); @@ -108,7 +109,7 @@ UserInfoWidget::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); - const auto sz = utils::calculateSidebarSizes(QFont{}); + const auto sz = splitter::calculateSidebarSizes(QFont{}); if (width() <= sz.small) { topLayout_->setContentsMargins(0, 0, logoutButtonSize_, 0); @@ -134,14 +135,6 @@ UserInfoWidget::reset() userAvatar_->setLetter(QChar('?')); } -void -UserInfoWidget::setAvatar(const QImage &img) -{ - avatar_image_ = img; - userAvatar_->setImage(img); - update(); -} - void UserInfoWidget::setDisplayName(const QString &name) { @@ -160,6 +153,14 @@ UserInfoWidget::setUserId(const QString &userid) { user_id_ = userid; userIdLabel_->setText(userid); + update(); +} + +void +UserInfoWidget::setAvatar(const QString &url) +{ + userAvatar_->setImage(url); + update(); } void diff --git a/src/UserInfoWidget.h b/src/UserInfoWidget.h index 65de7be9..e1a925a4 100644 --- a/src/UserInfoWidget.h +++ b/src/UserInfoWidget.h @@ -31,11 +31,11 @@ class UserInfoWidget : public QWidget Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor) public: - UserInfoWidget(QWidget *parent = 0); + UserInfoWidget(QWidget *parent = nullptr); - void setAvatar(const QImage &img); void setDisplayName(const QString &name); void setUserId(const QString &userid); + void setAvatar(const QString &url); void reset(); diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index e3c0d190..2cac783c 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -18,14 +18,23 @@ #include #include #include +#include #include #include #include #include +#include +#include #include +#include #include +#include #include +#include +#include +#include +#include "Cache.h" #include "Config.h" #include "MatrixClient.h" #include "Olm.h" @@ -46,10 +55,13 @@ UserSettings::load() hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); isStartInTrayEnabled_ = settings.value("user/window/start_in_tray", false).toBool(); isGroupViewEnabled_ = settings.value("user/group_view", true).toBool(); + isMarkdownEnabled_ = settings.value("user/markdown_enabled", true).toBool(); isTypingNotificationsEnabled_ = settings.value("user/typing_notifications", true).toBool(); isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool(); - theme_ = settings.value("user/theme", "light").toString(); + theme_ = settings.value("user/theme", defaultTheme_).toString(); font_ = settings.value("user/font_family", "default").toString(); + avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); + emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); applyTheme(); @@ -69,6 +81,13 @@ UserSettings::setFontFamily(QString family) save(); } +void +UserSettings::setEmojiFontFamily(QString family) +{ + emojiFont_ = family; + save(); +} + void UserSettings::setTheme(QString theme) { @@ -107,13 +126,18 @@ UserSettings::save() settings.setValue("start_in_tray", isStartInTrayEnabled_); settings.endGroup(); + settings.setValue("avatar_circles", avatarCircles_); + settings.setValue("font_size", baseFontSize_); settings.setValue("typing_notifications", isTypingNotificationsEnabled_); settings.setValue("read_receipts", isReadReceiptsEnabled_); settings.setValue("group_view", isGroupViewEnabled_); + settings.setValue("markdown_enabled", isMarkdownEnabled_); settings.setValue("desktop_notifications", hasDesktopNotifications_); settings.setValue("theme", theme()); settings.setValue("font_family", font_); + settings.setValue("emoji_font_family", emojiFont_); + settings.endGroup(); } @@ -128,12 +152,12 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge : QWidget{parent} , settings_{settings} { - topLayout_ = new QVBoxLayout(this); + topLayout_ = new QVBoxLayout{this}; QIcon icon; icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - auto backBtn_ = new FlatButton(this); + auto backBtn_ = new FlatButton{this}; backBtn_->setMinimumSize(QSize(24, 24)); backBtn_->setIcon(icon); backBtn_->setIconSize(QSize(24, 24)); @@ -150,106 +174,65 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter); topBarLayout_->addStretch(1); - auto trayOptionLayout_ = new QHBoxLayout; - trayOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto trayLabel = new QLabel(tr("Minimize to tray"), this); - trayLabel->setFont(font); - trayToggle_ = new Toggle(this); + formLayout_ = new QFormLayout; - trayOptionLayout_->addWidget(trayLabel); - trayOptionLayout_->addWidget(trayToggle_, 0, Qt::AlignRight); + formLayout_->setLabelAlignment(Qt::AlignLeft); + formLayout_->setFormAlignment(Qt::AlignRight); + formLayout_->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + formLayout_->setRowWrapPolicy(QFormLayout::WrapLongRows); + formLayout_->setHorizontalSpacing(0); + + auto general_ = new QLabel{tr("GENERAL"), this}; + general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + general_->setFont(font); + + trayToggle_ = new Toggle{this}; + startInTrayToggle_ = new Toggle{this}; + avatarCircles_ = new Toggle{this}; + groupViewToggle_ = new Toggle{this}; + typingNotifications_ = new Toggle{this}; + readReceipts_ = new Toggle{this}; + markdownEnabled_ = new Toggle{this}; + desktopNotifications_ = new Toggle{this}; + scaleFactorCombo_ = new QComboBox{this}; + fontSizeCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QComboBox{this}; + emojiFontSelectionCombo_ = new QComboBox{this}; - auto startInTrayOptionLayout_ = new QHBoxLayout; - startInTrayOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto startInTrayLabel = new QLabel(tr("Start in tray"), this); - startInTrayLabel->setFont(font); - startInTrayToggle_ = new Toggle(this); if (!settings_->isTrayEnabled()) startInTrayToggle_->setDisabled(true); - startInTrayOptionLayout_->addWidget(startInTrayLabel); - startInTrayOptionLayout_->addWidget(startInTrayToggle_, 0, Qt::AlignRight); + avatarCircles_->setFixedSize(64, 48); - auto groupViewLayout = new QHBoxLayout; - groupViewLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto groupViewLabel = new QLabel(tr("Group's sidebar"), this); - groupViewLabel->setFont(font); - groupViewToggle_ = new Toggle(this); + auto uiLabel_ = new QLabel{tr("INTERFACE"), this}; + uiLabel_->setFixedHeight(uiLabel_->minimumHeight() + LayoutTopMargin); + uiLabel_->setAlignment(Qt::AlignBottom); + uiLabel_->setFont(font); - groupViewLayout->addWidget(groupViewLabel); - groupViewLayout->addWidget(groupViewToggle_, 0, Qt::AlignRight); - - auto typingLayout = new QHBoxLayout; - typingLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto typingLabel = new QLabel(tr("Typing notifications"), this); - typingLabel->setFont(font); - typingNotifications_ = new Toggle(this); - - typingLayout->addWidget(typingLabel); - typingLayout->addWidget(typingNotifications_, 0, Qt::AlignRight); - - auto receiptsLayout = new QHBoxLayout; - receiptsLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto receiptsLabel = new QLabel(tr("Read receipts"), this); - receiptsLabel->setFont(font); - readReceipts_ = new Toggle(this); - - receiptsLayout->addWidget(receiptsLabel); - receiptsLayout->addWidget(readReceipts_, 0, Qt::AlignRight); - - auto desktopLayout = new QHBoxLayout; - desktopLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto desktopLabel = new QLabel(tr("Desktop notifications"), this); - desktopLabel->setFont(font); - desktopNotifications_ = new Toggle(this); - - desktopLayout->addWidget(desktopLabel); - desktopLayout->addWidget(desktopNotifications_, 0, Qt::AlignRight); - - auto scaleFactorOptionLayout = new QHBoxLayout; - scaleFactorOptionLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto scaleFactorLabel = new QLabel(tr("Scale factor"), this); - scaleFactorLabel->setFont(font); - scaleFactorCombo_ = new QComboBox(this); for (double option = 1; option <= 3; option += 0.25) scaleFactorCombo_->addItem(QString::number(option)); - - scaleFactorOptionLayout->addWidget(scaleFactorLabel); - scaleFactorOptionLayout->addWidget(scaleFactorCombo_, 0, Qt::AlignRight); - - auto fontSizeOptionLayout = new QHBoxLayout; - fontSizeOptionLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto fontSizeLabel = new QLabel(tr("Font size"), this); - fontSizeLabel->setFont(font); - fontSizeCombo_ = new QComboBox(this); for (double option = 10; option < 17; option += 0.5) fontSizeCombo_->addItem(QString("%1 ").arg(QString::number(option))); - fontSizeOptionLayout->addWidget(fontSizeLabel); - fontSizeOptionLayout->addWidget(fontSizeCombo_, 0, Qt::AlignRight); - - auto fontFamilyOptionLayout = new QHBoxLayout; - fontFamilyOptionLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto fontFamilyLabel = new QLabel(tr("Font Family"), this); - fontFamilyLabel->setFont(font); - fontSelectionCombo_ = new QComboBox(this); QFontDatabase fontDb; auto fontFamilies = fontDb.families(); for (const auto &family : fontFamilies) { fontSelectionCombo_->addItem(family); } - int fontIndex = fontSelectionCombo_->findText(settings_->font()); - fontSelectionCombo_->setCurrentIndex(fontIndex); + // TODO: Is there a way to limit to just emojis, rather than + // all emoji fonts? + auto emojiFamilies = fontDb.families(QFontDatabase::Symbol); + for (const auto &family : emojiFamilies) { + emojiFontSelectionCombo_->addItem(family); + } - fontFamilyOptionLayout->addWidget(fontFamilyLabel); - fontFamilyOptionLayout->addWidget(fontSelectionCombo_, 0, Qt::AlignRight); + fontSelectionCombo_->setCurrentIndex(fontSelectionCombo_->findText(settings_->font())); - auto themeOptionLayout_ = new QHBoxLayout; - themeOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto themeLabel_ = new QLabel(tr("Theme"), this); - themeLabel_->setFont(font); - themeCombo_ = new QComboBox(this); + emojiFontSelectionCombo_->setCurrentIndex( + emojiFontSelectionCombo_->findText(settings_->emojiFont())); + + themeCombo_ = new QComboBox{this}; themeCombo_->addItem("Light"); themeCombo_->addItem("Dark"); themeCombo_->addItem("System"); @@ -259,117 +242,103 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge int themeIndex = themeCombo_->findText(themeStr); themeCombo_->setCurrentIndex(themeIndex); - themeOptionLayout_->addWidget(themeLabel_); - themeOptionLayout_->addWidget(themeCombo_, 0, Qt::AlignRight); - - auto encryptionLayout_ = new QVBoxLayout; - encryptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin); - encryptionLayout_->setAlignment(Qt::AlignVCenter); + auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; + encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); + encryptionLabel_->setAlignment(Qt::AlignBottom); + encryptionLabel_->setFont(font); QFont monospaceFont; monospaceFont.setFamily("Monospace"); monospaceFont.setStyleHint(QFont::Monospace); monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9); - auto deviceIdLayout = new QHBoxLayout; - deviceIdLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - - auto deviceIdLabel = new QLabel(tr("Device ID"), this); - deviceIdLabel->setFont(font); - deviceIdLabel->setMargin(0); deviceIdValue_ = new QLabel{this}; deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); deviceIdValue_->setFont(monospaceFont); - deviceIdLayout->addWidget(deviceIdLabel, 1); - deviceIdLayout->addWidget(deviceIdValue_); - auto deviceFingerprintLayout = new QHBoxLayout; - deviceFingerprintLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - - auto deviceFingerprintLabel = new QLabel(tr("Device Fingerprint"), this); - deviceFingerprintLabel->setFont(font); - deviceFingerprintLabel->setMargin(0); deviceFingerprintValue_ = new QLabel{this}; deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); deviceFingerprintValue_->setFont(monospaceFont); - deviceFingerprintLayout->addWidget(deviceFingerprintLabel, 1); - deviceFingerprintLayout->addWidget(deviceFingerprintValue_); - auto sessionKeysLayout = new QHBoxLayout; - sessionKeysLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); - auto sessionKeysLabel = new QLabel(tr("Session Keys"), this); + deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X'))); + + auto sessionKeysLabel = new QLabel{tr("Session Keys"), this}; sessionKeysLabel->setFont(font); - sessionKeysLayout->addWidget(sessionKeysLabel, 1); + sessionKeysLabel->setMargin(OptionMargin); auto sessionKeysImportBtn = new QPushButton{tr("IMPORT"), this}; - connect( - sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); auto sessionKeysExportBtn = new QPushButton{tr("EXPORT"), this}; - connect( - sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys); + + auto sessionKeysLayout = new QHBoxLayout; + sessionKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); - encryptionLayout_->addLayout(deviceIdLayout); - encryptionLayout_->addLayout(deviceFingerprintLayout); - encryptionLayout_->addWidget(new HorizontalLine{this}); - encryptionLayout_->addLayout(sessionKeysLayout); + auto boxWrap = [this, &font](QString labelText, QWidget *field) { + auto label = new QLabel{labelText, this}; + label->setFont(font); + label->setMargin(OptionMargin); - font.setWeight(QFont::Medium); + auto layout = new QHBoxLayout; + layout->addWidget(field, 0, Qt::AlignRight); - auto encryptionLabel_ = new QLabel(tr("ENCRYPTION"), this); - encryptionLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - encryptionLabel_->setFont(font); + formLayout_->addRow(label, layout); + }; - auto general_ = new QLabel(tr("GENERAL"), this); - general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - general_->setFont(font); + formLayout_->addRow(general_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Minimize to tray"), trayToggle_); + boxWrap(tr("Start in tray"), startInTrayToggle_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Circular Avatars"), avatarCircles_); + boxWrap(tr("Group's sidebar"), groupViewToggle_); + boxWrap(tr("Typing notifications"), typingNotifications_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Read receipts"), readReceipts_); + boxWrap(tr("Send messages as Markdown"), markdownEnabled_); + boxWrap(tr("Desktop notifications"), desktopNotifications_); + formLayout_->addRow(uiLabel_); + formLayout_->addRow(new HorizontalLine{this}); - mainLayout_ = new QVBoxLayout; - mainLayout_->setAlignment(Qt::AlignTop); - mainLayout_->setSpacing(7); - mainLayout_->setContentsMargins( - sideMargin_, LayoutTopMargin, sideMargin_, LayoutBottomMargin); - mainLayout_->addWidget(general_, 1, Qt::AlignLeft | Qt::AlignBottom); - mainLayout_->addWidget(new HorizontalLine(this)); - mainLayout_->addLayout(trayOptionLayout_); - mainLayout_->addLayout(startInTrayOptionLayout_); - mainLayout_->addWidget(new HorizontalLine(this)); - mainLayout_->addLayout(groupViewLayout); - mainLayout_->addWidget(new HorizontalLine(this)); - mainLayout_->addLayout(typingLayout); - mainLayout_->addLayout(receiptsLayout); - mainLayout_->addLayout(desktopLayout); - mainLayout_->addWidget(new HorizontalLine(this)); - -#if defined(Q_OS_MAC) - scaleFactorLabel->hide(); +#if !defined(Q_OS_MAC) + boxWrap(tr("Scale factor"), scaleFactorCombo_); +#else scaleFactorCombo_->hide(); #endif + boxWrap(tr("Font size"), fontSizeCombo_); + boxWrap(tr("Font Family"), fontSelectionCombo_); - mainLayout_->addLayout(scaleFactorOptionLayout); - mainLayout_->addLayout(fontSizeOptionLayout); - mainLayout_->addLayout(fontFamilyOptionLayout); - mainLayout_->addWidget(new HorizontalLine(this)); - mainLayout_->addLayout(themeOptionLayout_); - mainLayout_->addWidget(new HorizontalLine(this)); +#if !defined(Q_OS_MAC) + boxWrap(tr("Emoji Font Family"), emojiFontSelectionCombo_); +#else + emojiFontSelectionCombo_->hide(); +#endif - mainLayout_->addSpacing(50); + boxWrap(tr("Theme"), themeCombo_); + formLayout_->addRow(encryptionLabel_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Device ID"), deviceIdValue_); + boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_); + formLayout_->addRow(new HorizontalLine{this}); + formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); - mainLayout_->addWidget(encryptionLabel_, 1, Qt::AlignLeft | Qt::AlignBottom); - mainLayout_->addWidget(new HorizontalLine(this)); - mainLayout_->addLayout(encryptionLayout_); - - auto scrollArea_ = new QScrollArea(this); + auto scrollArea_ = new QScrollArea{this}; scrollArea_->setFrameShape(QFrame::NoFrame); scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); scrollArea_->setWidgetResizable(true); scrollArea_->setAlignment(Qt::AlignTop | Qt::AlignVCenter); - auto scrollAreaContents_ = new QWidget(this); + QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); + + auto spacingAroundForm = new QHBoxLayout; + spacingAroundForm->addStretch(1); + spacingAroundForm->addLayout(formLayout_, 0); + spacingAroundForm->addStretch(1); + + auto scrollAreaContents_ = new QWidget{this}; scrollAreaContents_->setObjectName("UserSettingScrollWidget"); - scrollAreaContents_->setLayout(mainLayout_); + scrollAreaContents_->setLayout(spacingAroundForm); scrollArea_->setWidget(scrollAreaContents_); topLayout_->addLayout(topBarLayout_); @@ -392,6 +361,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge connect(fontSelectionCombo_, static_cast(&QComboBox::activated), [this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); + connect(emojiFontSelectionCombo_, + static_cast(&QComboBox::activated), + [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); connect(trayToggle_, &Toggle::toggled, this, [this](bool isDisabled) { settings_->setTray(!isDisabled); if (isDisabled) { @@ -410,6 +382,14 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge settings_->setGroupView(!isDisabled); }); + connect(avatarCircles_, &Toggle::toggled, this, [this](bool isDisabled) { + settings_->setAvatarCircles(!isDisabled); + }); + + connect(markdownEnabled_, &Toggle::toggled, this, [this](bool isDisabled) { + settings_->setMarkdownEnabled(!isDisabled); + }); + connect(typingNotifications_, &Toggle::toggled, this, [this](bool isDisabled) { settings_->setTypingNotifications(!isDisabled); }); @@ -422,6 +402,12 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge settings_->setDesktopNotifications(!isDisabled); }); + connect( + sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); + + connect( + sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys); + connect(backBtn_, &QPushButton::clicked, this, [this]() { settings_->save(); emit moveBack(); @@ -440,8 +426,10 @@ UserSettingsPage::showEvent(QShowEvent *) trayToggle_->setState(!settings_->isTrayEnabled()); startInTrayToggle_->setState(!settings_->isStartInTrayEnabled()); groupViewToggle_->setState(!settings_->isGroupViewEnabled()); + avatarCircles_->setState(!settings_->isAvatarCirclesEnabled()); typingNotifications_->setState(!settings_->isTypingNotificationsEnabled()); readReceipts_->setState(!settings_->isReadReceiptsEnabled()); + markdownEnabled_->setState(!settings_->isMarkdownEnabled()); desktopNotifications_->setState(!settings_->hasDesktopNotifications()); deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); @@ -449,16 +437,6 @@ UserSettingsPage::showEvent(QShowEvent *) utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); } -void -UserSettingsPage::resizeEvent(QResizeEvent *event) -{ - sideMargin_ = width() * 0.2; - mainLayout_->setContentsMargins( - sideMargin_, LayoutTopMargin, sideMargin_, LayoutBottomMargin); - - QWidget::resizeEvent(event); -} - void UserSettingsPage::paintEvent(QPaintEvent *) { @@ -471,7 +449,9 @@ UserSettingsPage::paintEvent(QPaintEvent *) void UserSettingsPage::importSessionKeys() { - auto fileName = QFileDialog::getOpenFileName(this, tr("Open Sessions File"), "", ""); + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const QString fileName = + QFileDialog::getOpenFileName(this, tr("Open Sessions File"), homeFolder, ""); QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { @@ -498,9 +478,9 @@ UserSettingsPage::importSessionKeys() } try { - auto sessions = mtx::crypto::decrypt_exported_sessions( - mtx::crypto::base642bin(payload), password.toStdString()); - cache::client()->importSessionKeys(std::move(sessions)); + auto sessions = + mtx::crypto::decrypt_exported_sessions(payload, password.toStdString()); + cache::importSessionKeys(std::move(sessions)); } catch (const mtx::crypto::sodium_exception &e) { QMessageBox::warning(this, tr("Error"), e.what()); } catch (const lmdb::error &e) { @@ -530,11 +510,12 @@ UserSettingsPage::exportSessionKeys() } // Open file dialog to save the file. - auto fileName = + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const QString fileName = QFileDialog::getSaveFileName(this, tr("File to save the exported session keys"), "", ""); QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("Error"), file.errorString()); return; } @@ -542,11 +523,16 @@ UserSettingsPage::exportSessionKeys() // Export sessions & save to file. try { auto encrypted_blob = mtx::crypto::encrypt_exported_sessions( - cache::client()->exportSessionKeys(), password.toStdString()); + cache::exportSessionKeys(), password.toStdString()); - auto b64 = mtx::crypto::bin2base64(encrypted_blob); + QString b64 = QString::fromStdString(mtx::crypto::bin2base64(encrypted_blob)); - file.write(b64.data(), b64.size()); + QString prefix("-----BEGIN MEGOLM SESSION DATA-----"); + QString suffix("-----END MEGOLM SESSION DATA-----"); + QString newline("\n"); + QTextStream out(&file); + out << prefix << newline << b64 << newline << suffix; + file.close(); } catch (const mtx::crypto::sodium_exception &e) { QMessageBox::warning(this, tr("Error"), e.what()); } catch (const lmdb::error &e) { diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 900f57e4..a1b7b084 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -19,9 +19,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -56,6 +58,7 @@ public: void setFontSize(double size); void setFontFamily(QString family); + void setEmojiFontFamily(QString family); void setGroupView(bool state) { @@ -66,6 +69,12 @@ public: save(); } + void setMarkdownEnabled(bool state) + { + isMarkdownEnabled_ = state; + save(); + } + void setReadReceipts(bool state) { isReadReceiptsEnabled_ = state; @@ -84,29 +93,46 @@ public: save(); } - QString theme() const { return !theme_.isEmpty() ? theme_ : "light"; } + void setAvatarCircles(bool state) + { + avatarCircles_ = state; + save(); + } + + QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } bool isTrayEnabled() const { return isTrayEnabled_; } bool isStartInTrayEnabled() const { return isStartInTrayEnabled_; } bool isGroupViewEnabled() const { return isGroupViewEnabled_; } + bool isAvatarCirclesEnabled() const { return avatarCircles_; } + bool isMarkdownEnabled() const { return isMarkdownEnabled_; } bool isTypingNotificationsEnabled() const { return isTypingNotificationsEnabled_; } bool isReadReceiptsEnabled() const { return isReadReceiptsEnabled_; } bool hasDesktopNotifications() const { return hasDesktopNotifications_; } double fontSize() const { return baseFontSize_; } QString font() const { return font_; } + QString emojiFont() const { return emojiFont_; } signals: void groupViewStateChanged(bool state); private: + // Default to system theme if QT_QPA_PLATFORMTHEME var is set. + QString defaultTheme_ = + QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() + ? "light" + : "system"; QString theme_; bool isTrayEnabled_; bool isStartInTrayEnabled_; bool isGroupViewEnabled_; + bool isMarkdownEnabled_; bool isTypingNotificationsEnabled_; bool isReadReceiptsEnabled_; bool hasDesktopNotifications_; + bool avatarCircles_; double baseFontSize_; QString font_; + QString emojiFont_; }; class HorizontalLine : public QFrame @@ -122,11 +148,10 @@ class UserSettingsPage : public QWidget Q_OBJECT public: - UserSettingsPage(QSharedPointer settings, QWidget *parent = 0); + UserSettingsPage(QSharedPointer settings, QWidget *parent = nullptr); protected: void showEvent(QShowEvent *event) override; - void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; signals: @@ -141,8 +166,8 @@ private slots: private: // Layouts QVBoxLayout *topLayout_; - QVBoxLayout *mainLayout_; QHBoxLayout *topBarLayout_; + QFormLayout *formLayout_; // Shared settings object. QSharedPointer settings_; @@ -152,7 +177,9 @@ private: Toggle *groupViewToggle_; Toggle *typingNotifications_; Toggle *readReceipts_; + Toggle *markdownEnabled_; Toggle *desktopNotifications_; + Toggle *avatarCircles_; QLabel *deviceFingerprintValue_; QLabel *deviceIdValue_; @@ -160,6 +187,7 @@ private: QComboBox *scaleFactorCombo_; QComboBox *fontSizeCombo_; QComboBox *fontSelectionCombo_; + QComboBox *emojiFontSelectionCombo_; int sideMargin_ = 0; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index f8fdfaf9..33b75894 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -3,25 +3,84 @@ #include #include #include +#include +#include +#include #include #include #include -#include -#include +#include +#include + #include +#include "Cache.h" #include "Config.h" +#include "MatrixClient.h" using TimelineEvent = mtx::events::collections::TimelineEvents; QHash authorColors_; +template +static DescInfo +createDescriptionInfo(const Event &event, const QString &localUser, const QString &room_id) +{ + const auto msg = std::get(event); + const auto sender = QString::fromStdString(msg.sender); + + const auto username = cache::displayName(room_id, sender); + const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); + + return DescInfo{ + QString::fromStdString(msg.event_id), + sender, + utils::messageDescription( + username, QString::fromStdString(msg.content.body).trimmed(), sender == localUser), + utils::descriptiveTime(ts), + ts}; +} + QString utils::localUser() { + return QString::fromStdString(http::client()->user_id().to_string()); +} + +QString +utils::replaceEmoji(const QString &body) +{ + QString fmtBody = ""; + + QVector utf32_string = body.toUcs4(); + QSettings settings; - return settings.value("auth/user_id").toString(); + QString userFontFamily = settings.value("user/emoji_font_family", "emoji").toString(); + + bool insideFontBlock = false; + for (auto &code : utf32_string) { + // TODO: Be more precise here. + if ((code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) || + (code >= 0x1f000 && code <= 0x1faff)) { + if (!insideFontBlock) { + fmtBody += QString(""); + insideFontBlock = true; + } + + } else { + if (insideFontBlock) { + fmtBody += ""; + insideFontBlock = false; + } + } + fmtBody += QString::fromUcs4(&code, 1); + } + if (insideFontBlock) { + fmtBody += ""; + } + + return fmtBody; } void @@ -37,7 +96,7 @@ utils::setScaleFactor(float factor) float utils::scaleFactor() { - QSettings settings("nheko", "nheko"); + QSettings settings; return settings.value("settings/scale_factor", -1).toFloat(); } @@ -74,13 +133,13 @@ utils::descriptiveTime(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return then.toString("HH:mm"); + return then.time().toString(Qt::DefaultLocaleShortDate); else if (days < 2) - return QString("Yesterday"); - else if (days < 365) - return then.toString("dd/MM"); + return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); + else if (days < 7) + return then.toString("dddd"); - return then.toString("dd/MM/yy"); + return then.date().toString(Qt::DefaultLocaleShortDate); } DescInfo @@ -97,39 +156,33 @@ utils::getMessageDescription(const TimelineEvent &event, using Video = mtx::events::RoomEvent; using Encrypted = mtx::events::EncryptedEvent; - if (boost::get